Explorar o código

Merge remote-tracking branch 'origin/master'

吴树波 hai 4 días
pai
achega
163c959f2d

+ 2 - 2
.env.prod-drk

@@ -5,9 +5,9 @@ VUE_APP_TITLE_INDEX =德瑞康互联网医院
 # 公司名称
 VUE_APP_COMPANY_NAME =青岛德瑞康医疗管理有限公司市北德瑞康中医医院
 # ICP备案号
-VUE_APP_ICP_RECORD =
+VUE_APP_ICP_RECORD =鲁ICP备2024135071号-3
 # ICP网站访问地址
-VUE_APP_ICP_URL =
+VUE_APP_ICP_URL =https://beian.miit.gov.cn
 # 网站LOG
 VUE_APP_LOG_URL =@/assets/logo/drk.png
 # 存储桶配置

+ 2 - 2
.env.prod-fcky

@@ -1,7 +1,7 @@
 # 页面标题
-VUE_APP_TITLE =蜂巢快药客户管理系统
+VUE_APP_TITLE =CRM客户管理系统
 # 首页菜单标题
-VUE_APP_TITLE_INDEX =蜂巢快药客户管理系统
+VUE_APP_TITLE_INDEX =CRM客户管理系统
 # 公司名称
 VUE_APP_COMPANY_NAME =蜂巢快药(北京)医药连锁有限公司
 # ICP备案号

+ 4 - 4
.env.prod-xfk

@@ -1,9 +1,9 @@
 # 页面标题
-VUE_APP_TITLE =小访客管理系统
+VUE_APP_TITLE =协和魔丽管理系统
 # 首页菜单标题
-VUE_APP_TITLE_INDEX =小访客
+VUE_APP_TITLE_INDEX =协和魔丽
 # 公司名称
-VUE_APP_COMPANY_NAME =西安小访客网络科技有限公司
+VUE_APP_COMPANY_NAME =西安协和魔丽网络科技有限公司
 # ICP备案号
 VUE_APP_ICP_RECORD =陕ICP备2025066365号-3
 # ICP网站访问地址
@@ -34,7 +34,7 @@ ENV = 'development'
 VUE_APP_BASE_API = '/prod-api'
 
 #默认 1、会员 2、企微
-VUE_APP_COURSE_DEFAULT = 1
+VUE_APP_COURSE_DEFAULT = 2
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 7 - 7
src/api/course/userCourseComplaintRecord.js

@@ -9,13 +9,13 @@ export function listUserCourseComplaintRecord(query) {
   })
 }
 
-// // 查询看课投诉记录详细
-// export function getUserCourseComplaintRecord(recordId) {
-//   return request({
-//     url: '/course/userCourseComplaintRecord/' + recordId,
-//     method: 'get'
-//   })
-// }
+// 查询看课投诉记录详细
+export function getUserCourseComplaintRecord(recordId) {
+  return request({
+    url: '/course/userCourseComplaintRecord/' + recordId,
+    method: 'get'
+  })
+}
 //
 // // 新增看课投诉记录
 // export function addUserCourseComplaintRecord(data) {

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

@@ -181,3 +181,21 @@ export function delPeriodDay(periodId) {
     method: 'delete'
   })
 }
+
+
+export function periodCourseStatisticCount(data) {
+  return request({
+    url: '/course/period/periodCourseStatisticCount',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询该会员下该训练营下操作过的所有营期
+export function periodList(data) {
+  return request({
+    url: '/course/period/periodlist',
+    method: 'post',
+    data: data
+  })
+}

+ 10 - 0
src/api/his/userOperationLog.js

@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 查询用户操作日志对象列表
+export function listUserOperationLog(query) {
+  return request({
+    url: '/his/userOperationLog/list',
+    method: 'get',
+    params: query
+  })
+}

+ 7 - 7
src/components/ImageUpload/index.vue

@@ -165,8 +165,8 @@ export default {
           // 文件大于1MB时进行压缩
           this.compressImage(file).then((compressedFile) => {
             loadingInstance.close();
-            if (compressedFile.size / 1024 > 500) {
-              this.$message.error('图片压缩后仍大于500KB');
+            if (compressedFile.size / 1024 > 1000) {
+              this.$message.error('图片压缩后仍大于1000KB');
               reject();
             } else {
               // this.$message.success(`图片压缩成功,最终质量为: ${this.finalQuality.toFixed(2)}`);
@@ -195,7 +195,7 @@ export default {
       //     return false;
       //   }
       // }
-      
+
     },
     compressImage(file) {
       return new Promise((resolve, reject) => {
@@ -215,16 +215,16 @@ export default {
 
             let quality = 1; // 初始压缩质量
             let dataURL = canvas.toDataURL('image/jpeg', quality);
-            
+
             // 逐步压缩,直到图片大小小于500KB并且压缩质量不再降低
             while (dataURL.length / 1024 > 500 && quality > 0.1) {
               quality -= 0.01;
               dataURL = canvas.toDataURL('image/jpeg', quality);
             }
             this.finalQuality = quality; // 存储最终的压缩质量
-            
-            if (dataURL.length / 1024 > 500) {
-              reject(new Error('压缩后图片仍然大于500KB'));
+
+            if (dataURL.length / 1024 > 1000) {
+              reject(new Error('压缩后图片仍然大于1000KB'));
               return;
             }
 

+ 4 - 4
src/components/Material/index.vue

@@ -367,8 +367,8 @@ export default {
           // 文件大于1MB时进行压缩
           this.compressImage(file).then((compressedFile) => {
             loadingInstance.close();
-            if (compressedFile.size / 1024 > 500) {
-              this.$message.error('图片压缩后仍大于500KB');
+            if (compressedFile.size / 1024 > 1000) {
+              this.$message.error('图片压缩后仍大于1M');
               reject();
             } else {
               // this.$message.success(`图片压缩成功,最终质量为: ${this.finalQuality.toFixed(2)}`);
@@ -417,8 +417,8 @@ export default {
             }
             this.finalQuality = quality; // 存储最终的压缩质量
 
-            if (dataURL.length / 1024 > 500) {
-              reject(new Error('压缩后图片仍然大于500KB'));
+            if (dataURL.length / 1024 > 1000) {
+              reject(new Error('压缩后图片仍然大于1000KB'));
               return;
             }
 

+ 2 - 1
src/views/components/his/userAddDetails.vue

@@ -115,6 +115,8 @@ export default {
       open: false,
       cityIds:null,
       address:null,
+      citys:[],
+
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -179,7 +181,6 @@ export default {
     });
   },
   methods: {
-     citys:[],
     /** 查询用户地址列表 */
     getList() {
       this.loading = true;

+ 2 - 0
src/views/components/his/userDetails.vue

@@ -1,9 +1,11 @@
 <template>
 
 <div style="background-color: #f0f2f5; padding-bottom: 20px; min-height: 100%; " >
+<!--
   <div style="padding: 20px; background-color: #fff;">
     会员详情
   </div>
+-->
 
   <div class="contentx" v-if="item!=null" >
     <div class="desct">

+ 2 - 40
src/views/course/courseWatchLog/index.vue

@@ -1,31 +1,6 @@
 <template>
   <div class="app-container">
     <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
-      <el-form-item label="用户" prop="userId">
-        <el-select v-model="queryParams.userId" remote placeholder="用户名/手机号" filterable clearable  style="width: 100%;"
-                   @keyup.enter.native="handleQuery"
-                   :remote-method="remoteGetFsUserList"
-                   @clear="handleClear"
-                   :loading="queryUserLoading"
-        >
-          <el-option
-            v-for="dict in fsUserList"
-            :key="`${dict.nickname} - ${dict.phone}`"
-            :label="`${dict.nickname} - ${dict.phone}`"
-            :value="dict.userId">
-          </el-option>
-        </el-select>
-      </el-form-item>
-      <el-form-item label="所属销售" prop="companyUserName">
-        <el-select v-model="queryParams.companyUserId" remote placeholder="请选择" filterable clearable  style="width: 100%;" @keyup.enter.native="handleQuery">
-          <el-option
-            v-for="dict in companyUserList"
-            :key="`${dict.nickName} - ${dict.userName}`"
-            :label="`${dict.nickName} - ${dict.userName}`"
-            :value="dict.userId">
-          </el-option>
-        </el-select>
-      </el-form-item>
       <el-form-item label="课程" prop="courseId">
         <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
           <el-option
@@ -68,16 +43,7 @@
                         range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="updateChange"></el-date-picker>
       </el-form-item>
 
-      <el-form-item label="类型" prop="type">
-        <el-select filterable  v-model="sourceTypeModel" placeholder="请选择小节"  clearable size="small">
-          <el-option
-            v-for="dict in userSourceTypeOptions"
-            :key="dict.dictValue"
-            :label="dict.dictLabel"
-            :value="dict.dictValue"
-          />
-        </el-select>
-      </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>
@@ -235,11 +201,7 @@ export default {
     this.getDicts("sys_course_watch_log_type").then(response => {
       this.logTypeOptions = response.data;
     });
-    getUserList().then(res=>{
-      if(res.code === 200) {
-        this.companyUserList = res.data
-      }
-    })
+
 
     this.getDicts('user_source_type').then(response => {
       this.userSourceTypeOptions = response.data;

+ 19 - 1
src/views/course/userCourse/index.vue

@@ -31,6 +31,16 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
+      <el-form-item label="关联的公司" prop="companyIds">
+        <el-select v-model="queryParams.companyIdsList" multiple placeholder="请选择公司" filterable clearable style="width: 90%;">
+          <el-option
+            v-for="dict in companyOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
       <el-form-item label="课堂类型" prop="isPrivate" style="display: none">
         <el-select v-model="queryParams.isPrivate" placeholder="请选择" clearable size="small">
           <el-option
@@ -210,6 +220,13 @@
             </el-form-item>
           </el-col>
         </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="排序" prop="sort">
+                          <el-input-number v-model="form.sort"  :min="0"  label="排序"></el-input-number>
+            </el-form-item>
+          </el-col>
+        </el-row>
         <el-row>
           <el-col :span="24">
             <el-form-item label="课堂简介" prop="description">
@@ -343,7 +360,8 @@ export default {
         hotRanking: null,
         integral: null,
         price: null,
-        isPrivate: 1
+        isPrivate: 1,
+        companyIdsList:[],
       },
       // 表单参数
       form: {},

+ 139 - 37
src/views/course/userCourseComplaintRecord/index.vue

@@ -87,6 +87,13 @@
 <!--            @click="handleDelete(scope.row)"-->
 <!--            v-hasPermi="['course:userCourseComplaintRecord:remove']"-->
 <!--          >删除</el-button>-->
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-info"
+            @click="handleDetails(scope.row)"
+            v-hasPermi="['course:userCourseComplaintRecord:edit']"
+          >详情</el-button>
           <el-button
             size="mini"
             type="text"
@@ -106,32 +113,55 @@
       @pagination="getList"
     />
 
-    <!-- 添加或修改看课投诉记录对话框 -->
-<!--    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>-->
-<!--      <el-form ref="form" :model="form" :rules="rules" label-width="80px">-->
-<!--        <el-form-item label="用户昵称" prop="nickName">-->
-<!--          <el-input v-model="form.nickName" placeholder="请输入用户昵称" />-->
-<!--        </el-form-item>-->
-<!--        <el-form-item label="投诉类型" prop="complaintTypeName">-->
-<!--          <el-input v-model="form.complaintTypeName" placeholder="请输入投诉类型" />-->
+<!--     投诉记录详情对话框-->
+    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="用户昵称" prop="nickName">
+          <el-input v-model="form.nickName" readonly />
+        </el-form-item>
+        <el-form-item label="投诉类型" prop="complaintTypeName">
+          <el-input v-model="form.complaintTypeName" readonly />
+        </el-form-item>
+        <el-form-item label="投诉内容" prop="complaintContent">
+          <el-input v-model="form.complaintContent" type="textarea" :rows="4" readonly />
+        </el-form-item>
+        <el-form-item label="投诉上传图片">
+          <div v-if="imageList.length > 0" class="image-preview-container">
+            <div v-for="(imageUrl, index) in imageList" :key="index" class="image-item">
+              <el-image
+                :src="imageUrl"
+                :preview-src-list="imageList"
+                fit="cover"
+                class="preview-image"
+                @click="previewImage(index)"
+              >
+                <div slot="error" class="image-slot">
+                  <i class="el-icon-picture-outline"></i>
+                </div>
+              </el-image>
+            </div>
+          </div>
+          <span v-else class="no-image-text">暂无图片</span>
+        </el-form-item>
+        <el-form-item label="所属课程" prop="courseName">
+          <el-input v-model="form.courseName" readonly />
+        </el-form-item>
+        <el-form-item label="所属小节" prop="title">
+          <el-input v-model="form.title" readonly />
+        </el-form-item>
+<!--        <el-form-item label="创建时间" prop="createTime">-->
+<!--          <el-input v-model="form.createTime" readonly />-->
 <!--        </el-form-item>-->
-<!--        <el-form-item label="所属课程" prop="courseName">-->
-<!--          <el-input v-model="form.courseName" placeholder="请输入所属课程" />-->
-<!--        </el-form-item>-->
-<!--        <el-form-item label="所属小节" prop="title">-->
-<!--          <el-input v-model="form.title" placeholder="请输入所属小节" />-->
-<!--        </el-form-item>-->
-<!--      </el-form>-->
-<!--      <div slot="footer" class="dialog-footer">-->
-<!--        <el-button type="primary" @click="submitForm">确 定</el-button>-->
-<!--        <el-button @click="cancel">取 消</el-button>-->
-<!--      </div>-->
-<!--    </el-dialog>-->
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="cancel">关 闭</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import { listUserCourseComplaintRecord, delUserCourseComplaintRecord, exportUserCourseComplaintRecord } from "@/api/course/userCourseComplaintRecord";
+import { listUserCourseComplaintRecord, delUserCourseComplaintRecord, exportUserCourseComplaintRecord, getUserCourseComplaintRecord } from "@/api/course/userCourseComplaintRecord";
 import { addBlack } from "@/api/course/courseWatchComment";
 export default {
   name: "UserCourseComplaintRecord",
@@ -157,6 +187,8 @@ export default {
       title: "",
       // 是否显示弹出层
       open: false,
+      // 图片列表
+      imageList: [],
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -199,15 +231,17 @@ export default {
         recordId: null,
         nickName: null,
         complaintTypeName: null,
+        complaintContent: null,
+        complaintUrl: null,
         courseName: null,
         title: null,
-        createTime: null
-        // userId: null,
-        // complaintTypeId: null,
-        // complaintContent: null,
-        // courseId: null,
-        // videoId: null,
+        createTime: null,
+        userId: null,
+        complaintTypeId: null,
+        courseId: null,
+        videoId: null,
       };
+      this.imageList = [];
       this.resetForm("form");
     },
     /** 搜索按钮操作 */
@@ -232,16 +266,29 @@ export default {
     //   this.open = true;
     //   this.title = "添加看课投诉记录";
     // },
-    // /** 修改按钮操作 */
-    // handleUpdate(row) {
-    //   this.reset();
-    //   const recordId = row.recordId || this.ids
-    //   getUserCourseComplaintRecord(recordId).then(response => {
-    //     this.form = response.data;
-    //     this.open = true;
-    //     this.title = "修改看课投诉记录";
-    //   });
-    // },
+    /** 详情按钮操作 */
+    handleDetails(row) {
+      this.reset();
+      const recordId = row.recordId || this.ids
+      getUserCourseComplaintRecord(recordId).then(response => {
+        this.form = response.data;
+        // 处理多个图片URL,通过逗号分割
+        if (this.form.complaintUrl) {
+          this.imageList = this.form.complaintUrl.split(',').filter(url => url.trim() !== '');
+        } else {
+          this.imageList = [];
+        }
+        this.open = true;
+        this.title = "看课投诉记录详情";
+      }).catch(error => {
+        console.error('获取详情失败:', error);
+        this.msgError("获取详情失败,请稍后重试");
+      });
+    },
+    /** 预览图片 */
+    previewImage(index) {
+      // Element UI的图片组件会自动处理预览
+    },
     // /** 提交按钮 */
     // submitForm() {
     //   this.$refs["form"].validate(valid => {
@@ -315,3 +362,58 @@ export default {
   }
 };
 </script>
+
+<style scoped>
+.image-preview-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+
+.image-item {
+  width: 100px;
+  height: 100px;
+  border: 1px solid #dcdfe6;
+  border-radius: 6px;
+  overflow: hidden;
+  cursor: pointer;
+}
+
+.preview-image {
+  width: 100%;
+  height: 100%;
+}
+
+.image-slot {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 100%;
+  background: #f5f7fa;
+  color: #909399;
+  font-size: 20px;
+}
+
+.no-image-text {
+  color: #909399;
+  font-size: 14px;
+}
+
+/* 只读表单样式优化 */
+.el-input.is-disabled .el-input__inner,
+.el-input__inner[readonly] {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #606266;
+  cursor: not-allowed;
+}
+
+.el-textarea.is-disabled .el-textarea__inner,
+.el-textarea__inner[readonly] {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #606266;
+  cursor: not-allowed;
+}
+</style>

+ 5 - 5
src/views/his/company/index.vue

@@ -279,10 +279,10 @@
 <!--                              end: '24:00'-->
 <!--                            }"></el-time-select>-->
 <!--      </el-form-item>-->
-        <el-form-item label="公众号点播配置" prop="courseMaAppId">
+        <el-form-item label="服务号点播配置" prop="courseMaAppId">
           <el-select
             v-model="form.courseMaAppId"
-            placeholder="请选择公众号"
+            placeholder="请选择服务号"
             clearable
             size="small"
           >
@@ -297,7 +297,7 @@
       <el-form-item label="小程序点播配置" prop="courseMiniAppId">
         <el-select
           v-model="form.courseMiniAppId"
-          placeholder="请选择公众号"
+          placeholder="请选择小程序"
           clearable
           size="small"
         >
@@ -692,10 +692,10 @@ export default {
           let value = data?.configValue
           if (value) {
             const appList = JSON.parse(value);
-            this.maAppList = appList.filter(v => v.type === '1').map(v => {
+            this.maAppList = appList.filter(v => v.type === '2').map(v => {
               return { appId: v.appid, appName: v.name }
             })
-            this.miniAppList = appList.filter(v => v.type === '2').map(v => {
+            this.miniAppList = appList.filter(v => v.type === '1').map(v => {
               return { appId: v.appid, appName: v.name }
             })
           }

+ 14 - 12
src/views/his/doctor/type1.vue

@@ -315,7 +315,7 @@
         <el-row>
               <el-col :span="12">
                 <el-form-item label="科室" prop="deptId">
-                <el-select v-model="form.deptId" multiple  placeholder="请选择所属科室">
+                <el-select v-model="form.deptId"  placeholder="请选择所属科室">
                       <el-option
                           v-for="dict in depList"
                           :key="dict.dictValue"
@@ -950,9 +950,9 @@ export default {
       this.sexOptions = response.data;
     });
     this.getdeplist();
-	listStore().then(response => {
-	  this.storeOPtions = response.rows;
-	});
+    listStore().then(response => {
+      this.storeOPtions = response.rows;
+    });
     this.getHospitaldeplist();
     getAllFollowDoctorList().then(response => {
 
@@ -1298,9 +1298,9 @@ export default {
         if(this.form.packageIds!=null){
            this.form.packageIds= ((this.form.packageIds).split(",")).map(Number)
         }
-		if(this.form.storeIds!=null){
-		   this.form.storeIds= ((this.form.storeIds).split(",")).map(Number)
-		}
+        if(this.form.storeIds!=null){
+          this.form.storeIds= ((this.form.storeIds).split(",")).map(Number)
+        }
         if(this.form.isSelf!=null){
           this.form.isSelf = String(this.form.isSelf)
         }
@@ -1316,7 +1316,10 @@ export default {
         if(this.form.isPrescribeDoctor!=null){
           this.form.isPrescribeDoctor = String(this.form.isPrescribeDoctor)
         }
-        this.form.cityIds=((this.form.cityIds).split(",")).map(Number)
+        if(this.form.cityIds){
+          this.form.cityIds=((this.form.cityIds).split(",")).map(Number)
+        }
+        
         if(this.form.prescribeDoctorName!=null){
               this.doctorName.name=this.form.prescribeDoctorName;
               this.getlistdocuser();
@@ -1366,6 +1369,9 @@ export default {
     },
     /** 提交按钮 */
     submitForm() {
+      if(this.form.isPrescribeDoctor!=null){
+          this.form.isPrescribeDoctor = String(this.form.isPrescribeDoctor)
+        }
       this.$refs["form"].validate(valid => {
         if (valid) {
           if(this.form.packageIds!=null){
@@ -1374,10 +1380,6 @@ export default {
           if(this.form.storeIds!=null){
             this.form.storeIds=(this.form.storeIds).toString()
           }
-          if(this.form.deptId!=null){
-            this.form.deptId=this.form.deptId.join(",");
-            console.log(this.form.deptId)
-          }
           this.form.cityIds=(this.form.cityIds).toString()
           if (this.form.doctorId != null) {
             this.form.doctorType=1;

+ 42 - 39
src/views/his/user/index.vue

@@ -1,6 +1,40 @@
 <template>
   <div class="app-container">
     <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="所属公司" prop="companyName">
+        <el-select
+          v-model="queryCompanyId"
+          placeholder="请选择所属公司"
+          clearable
+          filterable
+          size="small"
+          @change="handleQueryCompanyChange"
+        >
+          <el-option
+            v-for="item in companyQueryOptions"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="所属销售" prop="companyUserId">
+
+        <el-select
+          v-model="queryCompanyUserId"
+          placeholder="请选择所属销售"
+          clearable
+          filterable
+          size="small"
+        >
+          <el-option
+            v-for="item in companyQueryUserOptions"
+            :key="item.userId"
+            :label="item.nickName"
+            :value="item.userId">
+          </el-option>
+        </el-select>
+      </el-form-item>
       <el-form-item label="会员ID" prop="userId">
         <el-input
           v-model="queryParams.userId"
@@ -67,47 +101,14 @@
         </el-select>
       </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="change"></el-date-picker>
+                <el-date-picker v-model="createTime" size="small" style="width: 230px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="change"></el-date-picker>
       </el-form-item>
-      <el-form-item label="所属公司" prop="companyName">
-        <el-select
-          v-model="queryCompanyId"
-          placeholder="请选择所属公司"
-          clearable
-          filterable 
-          size="small"
-          @change="handleQueryCompanyChange"
-        >
-          <el-option
-            v-for="item in companyQueryOptions"
-            :key="item.companyId"
-            :label="item.companyName"
-            :value="item.companyId">
-          </el-option>
-        </el-select>
-      </el-form-item>
-      <el-form-item label="所属销售" prop="companyUserId">
-        
-        <el-select
-          v-model="queryCompanyUserId"
-          placeholder="请选择所属销售"
-          clearable
-          filterable 
-          size="small"
-        >
-          <el-option
-            v-for="item in companyQueryUserOptions"
-            :key="item.userId"
-            :label="item.nickName"
-            :value="item.userId">
-          </el-option>
-        </el-select>
-      </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">
@@ -229,7 +230,8 @@
            :with-header="false"
             size="75%"
              :title="show.title" :visible.sync="show.open">
-         <userDetails  ref="userDetails" />
+<!--         <userDetails  ref="userDetails" />-->
+         <userDetailsByNew  ref="userDetailsByNew" />
        </el-drawer>
 
     <!-- 更换会员归属对话框 -->
@@ -268,10 +270,11 @@
 import { listUser, getUser, delUser, addUser, updateUser, exportUser } from "@/api/his/user";
 import { getCompanyUserList, changeCompanyUser, getCompanyList } from '@/api/company/companyUser';
 import userDetails from '../../components/his/userDetails.vue';
+import userDetailsByNew from './userDetails.vue';
 
 export default {
   name: "User",
-  components: {userDetails},
+  components: {userDetails,userDetailsByNew},
   data() {
     return {
       companyQueryOptions:[],
@@ -401,7 +404,7 @@ export default {
     handledetails(row){
             this.show.open=true;
             setTimeout(() => {
-                 this.$refs.userDetails.getDetails(row.userId);
+                 this.$refs.userDetailsByNew.getDetails(row.userId);
             }, 1);
      },
      handleQueryCompanyChange(companyId){

+ 460 - 0
src/views/his/user/userBehavior.vue

@@ -0,0 +1,460 @@
+<template>
+  <div class="behavior-track-container">
+    <!-- 筛选区域 -->
+    <div class="filter-section">
+      <div class="filter-item">
+        <label>操作类型:</label>
+        <el-select
+          v-model="queryParams.operationType"
+          class="type-select"
+          @change="handleTypeChange"
+          placeholder="请选择行为类型"
+          clearable
+        >
+          <el-option
+            v-for="item in typeList"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictLabel"
+          ></el-option>
+        </el-select>
+      </div>
+    </div>
+    <div v-if="steps.length === 0" class="no-data">
+      <p>暂无数据</p>
+    </div>
+    <!-- 按日期分组展示(分页后的数据) -->
+    <div v-if="steps.length != 0" v-for="(records, date) in groupedPaginatedSteps" :key="date" class="date-group">
+      <!-- 日期标题(如果是今天,显示“今天”,否则显示具体日期) -->
+      <div class="date-title">
+        {{ isToday(date) ? '今天' : date }}
+      </div>
+      <div class="date-divider"></div>
+
+      <!-- 当前日期下的记录 -->
+      <el-steps
+        direction="vertical"
+        :space="120"
+        process-status="process"
+        finish-status="success"
+        class="custom-steps"
+      >
+        <el-step
+          v-for="(step, index) in records"
+          :key="index"
+          :icon="index === 0 ? 'el-icon-s-help' : 'el-icon-bangzhu'"
+        >
+          <template #title>
+            <div class="step-title">
+              {{ step.operationType }}
+              <span class="step-time">{{ step.createTime }}</span> <!-- 每个步骤的时间 -->
+            </div>
+          </template>
+          <template #description>
+            <!-- 答题 -->
+            <div v-if="step.operationType == '答题'" class="step-content">
+              <!-- 第一行 -->
+              <div class="step-row first-row">
+                <div v-if="step.paramVo" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-notebook-2"></i> 课节名称:</span>
+                  <span class="detail-value">{{ step.paramVo.courseName }}</span>
+                </div>
+              </div>
+
+              <!-- 第二行 -->
+              <div v-if="step.details" class="step-row second-row">
+                <div class="detail-item">
+                  <span class="detail-label"><i class="el-icon-chat-dot-round"></i> 备注:</span>
+                  <span class="detail-value">{{ step.details }}</span>
+                </div>
+              </div>
+            </div>
+
+            <!-- 发放奖励 -->
+            <div v-else-if="step.operationType == '发送奖励'" class="step-content">
+              <!-- 第一行 -->
+              <div class="step-row first-row">
+                <div v-if="step.paramVo" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-gift"></i> 课程名称:</span>
+                  <span class="detail-value">{{ step.paramVo.courseName }}</span>
+                </div>
+                <div v-if="step.paramVo" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-coin"></i> 小节名称:</span>
+                  <span class="detail-value">{{ step.paramVo.title }}</span>
+                </div>
+                <div v-if="step.fsCourseRedPacketLog" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-coin"></i> 发放金额:</span>
+                  <span class="detail-value">{{ step.fsCourseRedPacketLog.amount }} 元</span>
+                </div>
+              </div>
+
+              <!-- 第二行 -->
+              <div v-if="step.details" class="step-row second-row">
+                <div class="detail-item">
+                  <span class="detail-label"><i class="el-icon-chat-dot-round"></i> 备注:</span>
+                  <span class="detail-value">{{ step.details }}</span>
+                </div>
+              </div>
+            </div>
+
+            <!-- 其他类型 -->
+            <div v-else class="step-content">
+              <!-- 第一行 -->
+              <div class="step-row first-row">
+                <div v-if="step.paramVo" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-office-building"></i> 训练营:</span>
+                  <span class="detail-value">{{ step.paramVo.trainingCampName }}</span>
+                </div>
+                <div v-if="step.paramVo" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-collection-tag"></i> 营期名称:</span>
+                  <span class="detail-value">{{ step.paramVo.periodName }}</span>
+                </div>
+                <div v-if="step.paramVo" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-notebook-2"></i> 课节名称:</span>
+                  <span class="detail-value">{{ step.paramVo.courseName }}</span>
+                </div>
+              </div>
+
+              <!-- 第二行 -->
+              <div v-if="step.details" class="step-row second-row">
+                <div class="detail-item">
+                  <span class="detail-label"><i class="el-icon-chat-dot-round"></i> 备注:</span>
+                  <span class="detail-value">{{ step.details }}</span>
+                </div>
+              </div>
+            </div>
+          </template>
+
+        </el-step>
+      </el-steps>
+    </div>
+
+    <!-- 分页 -->
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </div>
+
+
+</template>
+
+<script>
+import { listUserOperationLog} from "@/api/his/userOperationLog";
+
+export default {
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      typeList: [],
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        operationType:null,
+        userId:null,
+      },
+      total: 0,
+      steps: [
+      ]
+    };
+  },
+  created() {
+    this.getDicts("fs_user_operation_type").then(response => {
+      this.typeList = response.data;
+    });
+  },
+  computed: {
+    // 删除这个属性,直接在 getList 中使用后端的操作类型
+    groupedPaginatedSteps() {
+      const groups = {};
+      this.steps.forEach((step) => {
+        const date = step.createTime ?
+          (step.createTime instanceof Date ? step.createTime.toISOString().substr(0, 10) : new Date(step.createTime).toISOString().substr(0, 10))
+          : null;
+        if (!groups[date]) {
+          groups[date] = [];
+        }
+        groups[date].push(step);
+      });
+      return groups;
+    },
+
+  },
+  methods: {
+    getList() {
+      this.loading = true;
+      listUserOperationLog(this.queryParams).then(response => {
+        this.steps = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 判断是否为今天
+    isToday(date) {
+      const today = new Date();
+      const formattedDate = `${today.getFullYear()}-${(today.getMonth() + 1).toString().padStart(2, '0')}-${today.getDate().toString().padStart(2, '0')}`;
+      return date === formattedDate;
+    },
+    getDetails(orderId) {
+      this.queryParams.userId=orderId;
+      this.getList();
+
+    },
+    handleTypeChange() {
+      this.queryParams.pageNum = 1;  // 重置分页为第一页
+      this.getList();  // 重新获取数据
+    }
+  },
+};
+</script>
+
+<style scoped>
+.behavior-track-container {
+  width: 100%;
+  padding: 24px;
+  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
+  background-color: #f9fbfd;
+  border-radius: 12px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+}
+
+.filter-section {
+  margin-bottom: 24px;
+  background: #ffffff;
+  border-radius: 8px;
+  padding: 16px 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  display: flex;
+  align-items: center;
+}
+
+.filter-item {
+  display: flex;
+  align-items: center;
+}
+
+.filter-item label {
+  font-weight: 600;
+  color: #333;
+  margin-right: 12px;
+  font-size: 14px;
+}
+
+.type-select {
+  width: 240px;
+}
+
+.custom-steps {
+  padding: 0 16px;
+}
+
+.step-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #333;
+}
+
+.step-detail p {
+  margin: 8px 0;
+  line-height: 1.6;
+  font-size: 14px;
+  color: #555;
+}
+
+/* 步骤条样式优化 - 移除图标边框 */
+.custom-steps >>> .el-step__head {
+  padding-right: 16px;
+}
+
+.custom-steps >>> .el-step__icon {
+  width: 24px;
+  height: 24px;
+  font-size: 18px;
+}
+
+.custom-steps >>> .el-step__icon.is-icon {
+  color: #52c41a;
+}
+
+.custom-steps >>> .el-step__line {
+  top: 36px;
+  left: 11px;
+  background-color: #e8e8e8;
+}
+
+/* 分页样式优化 */
+.pagination-wrapper {
+  margin-top: 32px;
+  text-align: center;
+  padding: 16px 0;
+}
+
+.custom-pagination >>> .el-pager li {
+  border-radius: 4px;
+  margin: 0 4px;
+}
+
+.custom-pagination >>> .el-pager li.active {
+  background-color: #52c41a;
+  color: #fff;
+}
+
+.custom-pagination >>> .el-pagination__jump {
+  margin-left: 12px;
+}
+
+.custom-pagination >>> .el-input__inner {
+  border-radius: 4px;
+}
+
+/* 新增和修改的样式 */
+.step-content {
+  margin-top: 8px;
+  padding: 16px;
+  background: #ffffff;
+  border-radius: 8px;
+  border: 1px solid #ebeef5;
+  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.06);
+  transition: all 0.3s ease;
+}
+
+.step-content:hover {
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  transform: translateY(-1px);
+}
+
+.detail-label {
+  display: inline-flex;
+  align-items: center;
+  width: 100px;
+  color: #606266;
+  font-weight: 500;
+  flex-shrink: 0;
+}
+
+.detail-label i {
+  margin-right: 6px;
+  font-size: 14px;
+  color: #909399;
+}
+
+.detail-value {
+  flex: 1;
+  color: #303133;
+  word-break: break-word;
+  padding-left: 4px;
+}
+
+.step-row {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  margin-bottom: 8px;
+}
+
+.first-row .detail-item {
+  flex: 1 1 30%; /* 平均分布 */
+  display: flex;
+  align-items: center;
+  margin-right: 16px;
+}
+
+.second-row .detail-item {
+  display: flex;
+  align-items: center;
+}
+
+.time-item {
+  margin-left: auto; /* 时间靠右 */
+}
+
+.detail-label {
+  font-weight: 500;
+  color: #606266;
+  margin-right: 6px;
+}
+
+.detail-value {
+  color: #303133;
+  word-break: break-word;
+}
+
+@media (max-width: 768px) {
+  .first-row,
+  .second-row {
+    flex-direction: column;
+  }
+  .time-item {
+    margin-left: 0;
+  }
+}
+/* 完全重置步骤描述区域的样式 */
+.custom-steps >>> .el-step__description {
+  width: 100% !important;
+  max-width: 100% !important;
+  padding: 0 !important;
+  margin: 0 !important;
+}
+
+/* 确保步骤内容使用弹性布局 */
+.step-content {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+}
+
+/* 调整时间项 */
+.time-item {
+  align-self: flex-end; /* 替代 margin-left: auto */
+  margin-top: 8px; /* 如果需要与上方的间距 */
+}
+/* 日期标题样式 */
+.date-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: #333;
+  margin-top: 16px;
+}
+
+.date-divider {
+  height: 1px;
+  background: #e8e8e8;
+  margin: 4px 0 12px;
+}
+
+/* 在步骤条标题旁边显示时间 */
+.step-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.step-time {
+  font-size: 12px;
+  color: #999;
+  margin-left: 10px;
+}
+
+/* 第二行的备注 */
+.second-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.second-row .detail-item {
+  display: flex;
+  align-items: center;
+}
+
+.no-data {
+  text-align: center;
+  color: #999;
+  font-size: 18px;
+  padding: 20px;
+}
+</style>

+ 173 - 0
src/views/his/user/userCourseConversionRecord.vue

@@ -0,0 +1,173 @@
+<template>
+
+  <div style="background-color: #f0f2f5; padding-bottom: 20px; min-height: 100%; " >
+    <div class="contentx" v-if="item!=null">
+      <div class="desct"> 优惠劵领取信息</div>
+      <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 couponStatusOptions" :label="item.dictLabel" :name="item.dictValue"></el-tab-pane>
+      </el-tabs>
+      <el-table v-loading="loading" :data="userCouponList">
+        <el-table-column label="优惠劵标题" align="center" prop="title" />
+        <el-table-column label="券号" align="center" prop="couponCode" />
+        <el-table-column label="会员昵称" align="center" prop="nickName" />
+        <el-table-column label="会员电话" align="center" prop="phone" />
+        <el-table-column label="关联订单ID" align="center" prop="businessId" />
+        <el-table-column label="订单类型" align="center" prop="businessType">
+          <template slot-scope="scope">
+            <dict-tag :options="businessTypeOptions" :value="scope.row.businessType"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" align="center" prop="status">
+          <template slot-scope="scope">
+            <dict-tag :options="couponStatusOptions" :value="scope.row.status"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="领取时间" align="center" prop="createTime" width="180"/>
+        <el-table-column label="使用时间" align="center" prop="useTime" width="180"/>
+      </el-table>
+      <pagination
+        v-show="total>0"
+        :total="total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </div>
+
+    <div class="contentx" v-if="item!=null" >
+      <div class="desct">
+        用户药品订单
+      </div>
+      <userStorerDetails  ref="userDetails" />
+    </div>
+  </div>
+</template>
+
+
+
+<script>
+import { getPatientByUserId} from "@/api/his/patient";
+import { getUser ,getUserAddr} from "@/api/his/user";
+import { getListUserCoupon } from "@/api/his/userCoupon";
+import userStorerDetails from "../../components/his/userStorerDetails.vue";
+import userPatietDetails from "../../components/his/userPatietDetails.vue";
+import userInquiryOrderDetails from "../../components/his/userInquiryOrderDetails.vue";
+import userAddDetails from "../../components/his/userAddDetails.vue";
+export default {
+  name: "storedet",
+  props:["data"],
+  components: { userStorerDetails ,userInquiryOrderDetails,userPatietDetails,userAddDetails},
+  data() {
+    return {
+      patientInfo: process.env.VUE_APP_PATIENT_INFO,
+      addr:[],
+      patient:[],
+      userOptions: [],
+      statusOptions: [],
+      sexOptions: [],
+      pOptions: [],
+      item:null,
+      total: 0,
+      loading: true,
+      // 会员优惠券表格数据
+      userCouponList: [],
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        couponId: null,
+      },
+      actName:"10",
+      businessTypeOptions:[],
+      couponStatusOptions:[],
+    }
+  },
+  created() {
+    this.getDicts("sys_user_status").then(response => {
+      this.userOptions = response.data;
+    });
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+    this.getDicts("sys_patient_status").then(response => {
+      this.pOptions = response.data;
+    });
+    this.getDicts("sys_patient_sex").then(response => {
+      this.sexOptions = response.data;
+    });
+    this.getDicts("sys_coupon_business_type").then(response => {
+      this.businessTypeOptions = response.data;
+    });
+
+    this.getDicts("sys_coupon_status").then(response => {
+      this.couponStatusOptions = response.data;
+    });
+  },
+  methods: {
+    handleClickX(tab, event) {
+      if(tab.name=="10"){
+        this.queryParams.status=null;
+      }else{
+        this.queryParams.status=tab.name;
+      }
+      this.queryParams.pageNum = 1;
+      this.getList();
+
+    },
+    getList() {
+      this.loading = true;
+      getListUserCoupon(this.queryParams).then(response => {
+        this.userCouponList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+
+    getDetails(orderId) {
+      this.item=null;
+      getUser(orderId).then(response => {
+        this.item = response.data;
+        setTimeout(() => {
+          this.$refs.userDetails.getUserDetails(orderId);
+        }, 1);
+
+      });
+      this.patient=null;
+      getPatientByUserId(orderId).then(response => {
+        this.patient = response.data;
+      });
+      getUserAddr(orderId).then(response => {
+        this.addr = response.data;
+      });
+      this.queryParams.userId=orderId;
+      this.getList();
+
+    },
+  }
+}
+</script>
+<style>
+
+.contentx{
+  height: 100%;
+  background-color: #fff;
+  padding: 0px 20px 20px;
+
+
+  margin: 20px;
+}
+.el-descriptions-item__label.is-bordered-label{
+  font-weight: normal;
+}
+.el-descriptions-item__content {
+  max-width: 150px;
+  min-width: 100px;
+}
+.desct{
+  padding-top: 20px;
+  padding-bottom: 20px;
+  color: #524b4a;
+  font-weight: bold;
+}
+</style>

+ 476 - 0
src/views/his/user/userCoursePeriod.vue

@@ -0,0 +1,476 @@
+<template>
+  <div class="app-container">
+    <!-- 没有数据提示 -->
+    <div v-if="campList.length === 0 && !leftLoading" class="no-data">
+      <span>—— 没有数据 ——</span>
+    </div>
+
+    <el-container v-else>
+      <!-- 左侧区域 -->
+      <el-aside width="360px" class="left-aside">
+        <!-- 顶部区域 -->
+        <!-- 训练营列表 -->
+        <div class="camp-list" ref="campList" @scroll="handleScroll" v-loading="leftLoading">
+          <div
+            v-for="(item, index) in campList"
+            :key="index"
+            class="camp-item"
+            :class="{ 'active': activeCampIndex === index }"
+            @click="selectCamp(index)"
+          >
+            <div class="camp-content">
+              <div class="camp-title">
+                <i class="el-icon-s-flag camp-icon"></i>
+                {{ item.trainingCampName }}
+              </div>
+              <div class="camp-info">
+                <span>序号:{{ item.orderNumber }}</span>
+                <span>最新营期开课:{{ item.recentDate || '-' }}</span>
+              </div>
+            </div>
+          </div>
+          <!-- 底部加载更多提示 -->
+          <div v-if="loadingMore" class="loading-more">
+            <i class="el-icon-loading"></i>
+            <span>加载中...</span>
+          </div>
+
+          <!-- 所有数据加载完毕提示 -->
+          <div v-if="campList.length > 0 && !loadingMore" class="no-more-data">
+            <span>—— 已加载全部训练营 ——</span>
+          </div>
+        </div>
+      </el-aside>
+
+      <!-- 右侧区域 -->
+      <el-main><userCourseStatic  ref="userCourseStatic" /></el-main>
+    </el-container>
+
+  </div>
+</template>
+
+<script>
+import { listCamp} from "@/api/course/userCourseCamp";
+import userCourseStatic from './userCourseStatic.vue';
+export default {
+  name: "userCoursePeriod",
+  components: {
+    userCourseStatic
+  },
+  data() {
+    return {
+      // 加载更多状态
+      loadingMore: false,
+      // 激活的训练营索引
+      activeCampIndex: null,
+      // 训练营列表
+      campList: [],
+      // 遮罩层
+      loading: true,
+      updateDateOpen: false,
+      // 左侧遮罩层
+      leftLoading: true,
+      // 左侧查询参数
+      leftQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        hasNextPage: false,
+        scs: 'order_number(desc),training_camp_id(desc)',
+        trainingCampName: null,
+        userId:null,
+      },
+    };
+  },
+  created() {
+    // this.getLeftList();
+  },
+  methods: {
+    getDetails(orderId) {
+      this.userId=orderId;
+      this.leftQueryParams.userId=orderId;
+      this.getLeftList();
+    },
+    /** 查询左侧列表 */
+    getLeftList() {
+      this.leftLoading = true;
+      // 重置页码和加载更多状态
+      this.leftQueryParams.pageNum = 1;
+      this.loadingMore = false;
+
+      // 训练营数据
+      listCamp(this.leftQueryParams).then(response => {
+        if (response && response.code === 200) {
+          this.campList = response.data.list || [];
+          this.leftQueryParams.hasNextPage = response.data.hasNextPage;
+          this.activeCampIndex = this.campList.length > 0 ? 0 : null;
+          this.selectCamp(this.activeCampIndex);
+          // 如果当前显示的列表高度不足以触发滚动,但还有更多数据,自动加载下一页
+          this.$nextTick(() => {
+            const scrollEl = this.$refs.campList;
+            if (scrollEl && this.leftQueryParams.hasNextPage && scrollEl.scrollHeight <= scrollEl.clientHeight) {
+              this.loadMoreCamps();
+            }
+          });
+        } else {
+          this.$message.error(response.msg || '获取训练营列表失败');
+          this.campList = [];
+          this.leftQueryParams.hasNextPage = false;
+        }
+        this.leftLoading = false;
+      }).catch(error => {
+        console.error('获取训练营列表失败:', error);
+        this.$message.error('获取训练营列表失败');
+        this.campList = [];
+        this.leftQueryParams.hasNextPage = false;
+        this.leftLoading = false;
+      });
+    },
+    /** 选中训练营 */
+    selectCamp(index) {
+      if(index == null || index == undefined) return;
+      this.activeCampIndex = index;
+      // 加载对应的训练营营期数据
+      const selectedCamp = this.campList[index];
+      console.log(this.userId)
+      this.$refs.userCourseStatic.getDetails(selectedCamp,this.userId);
+      // this.queryParams.trainingCampId = selectedCamp.trainingCampId;
+      // this.getList();
+    },
+    /** 处理滚动事件,实现滚动到底部加载更多 */
+    handleScroll() {
+      // 如果正在节流中或者正在加载中,则不处理
+      if (this.scrollThrottle || this.loadingMore) return;
+
+      // 设置节流,200ms内不再处理滚动事件
+      this.scrollThrottle = true;
+      setTimeout(() => {
+        this.scrollThrottle = false;
+      }, 200);
+
+      const scrollEl = this.$refs.campList;
+      if (!scrollEl) return;
+
+      // 判断是否滚动到底部:滚动高度 + 可视高度 >= 总高度 - 30(添加30px的容差,提前触发加载)
+      const isBottom = scrollEl.scrollTop + scrollEl.clientHeight >= scrollEl.scrollHeight - 30;
+
+      // 如果滚动到底部,且有下一页数据,且当前不在加载中,则加载更多
+      if (isBottom && this.leftQueryParams.hasNextPage && !this.leftLoading && !this.loadingMore) {
+        this.loadMoreCamps();
+      }
+    },
+    /** 加载更多训练营数据 */
+    loadMoreCamps() {
+      // 已在加载中,防止重复加载
+      if (this.leftLoading || this.loadingMore) return;
+
+      // 设置加载状态
+      this.loadingMore = true;
+
+      // 页码加1
+      this.leftQueryParams.pageNum += 1;
+
+      // 加载下一页数据
+      listCamp(this.leftQueryParams).then(response => {
+        if (response && response.code === 200) {
+          // 将新数据追加到列表中
+          const newList = response.data.list || [];
+          if (newList.length > 0) {
+            this.campList = [...this.campList, ...newList];
+          }
+
+          // 更新是否有下一页的标志
+          this.leftQueryParams.hasNextPage = response.data.hasNextPage;
+
+          // 如果当前显示的列表高度不足以触发滚动,但还有更多数据,自动加载下一页
+          this.$nextTick(() => {
+            const scrollEl = this.$refs.campList;
+            if (scrollEl && this.leftQueryParams.hasNextPage && scrollEl.scrollHeight <= scrollEl.clientHeight) {
+              // 延迟一点再加载下一页,避免过快加载
+              setTimeout(() => {
+                this.loadMoreCamps();
+              }, 300);
+            }
+          });
+        } else {
+          this.$message.error(response.msg || '加载更多训练营失败');
+        }
+        this.loadingMore = false;
+      }).catch(error => {
+        console.error('加载更多训练营失败:', error);
+        this.$message.error('加载更多训练营失败');
+        this.loadingMore = false;
+      });
+    },
+
+  },
+};
+</script>
+
+<style scoped>
+.left-aside {
+  background-color: #fff;
+  border-right: 1px solid #EBEEF5;
+  padding: 0;
+  display: flex;
+  flex-direction: column;
+  height: 800px;
+}
+
+.left-header {
+  padding: 10px;
+  border-bottom: 1px solid #EBEEF5;
+}
+
+.left-header-top {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.search-btn {
+  width: 50%;
+  height: 36px;
+  background-color: #409EFF;
+  color: white;
+  border: none;
+}
+
+.search-input-wrapper {
+  margin-bottom: 10px;
+}
+
+.sort-wrapper {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.sort-label {
+  width: 70px;
+  font-size: 14px;
+  font-weight: 600;
+  color: #909399;
+}
+
+.sort-select {
+  margin-left: 10px;
+  width: 280px;
+}
+
+.color-wrapper {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.color-label {
+  width: 70px;
+  color: #606266;
+}
+
+.color-hint {
+  margin-left: 10px;
+  color: #606266;
+  font-size: 12px;
+}
+
+.color-help {
+  margin-left: 5px;
+  color: #909399;
+  cursor: pointer;
+}
+
+.hint-text {
+  color: #606266;
+  font-size: 12px;
+  margin-top: 5px;
+}
+
+.camp-list {
+  flex: 1;
+  overflow-y: auto;
+  padding: 3px;
+}
+
+.camp-item {
+  margin-bottom: 5px;
+  padding: 15px;
+  background-color: #ffffff;
+  position: relative;
+  cursor: pointer;
+  display: flex;
+  justify-content: space-between;
+  border: 1px solid #eaedf2;
+}
+
+.camp-item:last-child {
+  margin-bottom: 0;
+}
+
+.camp-item:hover {
+  background-color: #f5f9ff;
+}
+
+.camp-item.active {
+  background-color: #eaf4ff;
+  border-left: 1px solid #75b8fc;
+}
+
+.camp-content {
+  flex: 1;
+  padding-right: 10px;
+}
+
+.camp-title {
+  font-weight: bold;
+  font-size: 16px;
+  margin-bottom: 8px;
+  color: #333;
+  display: flex;
+  align-items: center;
+}
+
+.camp-icon {
+  font-size: 16px;
+  margin-right: 6px;
+  color: #409EFF;
+}
+
+.camp-info {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 8px;
+  font-size: 12px;
+  color: #c4c1c1;
+  line-height: 1.5;
+}
+
+.camp-stats {
+  display: flex;
+  justify-content: space-between;
+  font-size: 12px;
+  color: #666;
+  background-color: #f5f9ff;
+  padding: 6px 10px;
+  border-radius: 4px;
+  line-height: 1.5;
+}
+
+.stat-item {
+  display: flex;
+  align-items: center;
+}
+
+.stat-item i {
+  margin-right: 4px;
+  font-size: 14px;
+  color: #409EFF;
+}
+
+.camp-actions {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: flex-end;
+  gap: 8px;
+  border-left: 1px dashed #eaedf2;
+  padding-left: 12px;
+  min-width: 50px;
+}
+
+.action-btn {
+  padding: 2px 5px;
+  font-size: 12px;
+  border-radius: 4px;
+  transition: all 0.2s;
+}
+
+.action-btn:hover {
+  background-color: rgba(255, 255, 255, 0.8);
+}
+
+.delete-btn {
+  color: #f56c6c;
+}
+
+.delete-btn:hover {
+  background-color: rgba(245, 108, 108, 0.1);
+}
+
+.copy-btn {
+  color: #409EFF;
+}
+
+.copy-btn:hover {
+  background-color: rgba(64, 158, 255, 0.1);
+}
+
+.warning-icon {
+  color: #E6A23C;
+  font-size: 16px;
+  margin-left: 5px;
+}
+
+.el-main {
+  padding: 10px;
+}
+
+/* 添加训练营表单样式 */
+.drawer-footer {
+  /* position: absolute; */
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 20px;
+  background: #fff;
+  text-align: right;
+  border-top: 1px solid #e8e8e8;
+}
+
+.el-input-number {
+  width: 100%;
+}
+
+/* 加载更多样式 */
+.loading-more {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 12px 0;
+  color: #909399;
+  font-size: 14px;
+}
+
+.loading-more i {
+  margin-right: 5px;
+  font-size: 16px;
+}
+
+/* 无更多数据提示 */
+.no-more-data {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 12px 0;
+  color: #c0c4cc;
+  font-size: 13px;
+}
+
+.no-more-data span {
+  position: relative;
+  display: flex;
+  align-items: center;
+}
+/* 添加没有数据提示的样式 */
+.no-data {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100vh;  /* 让提示居中,覆盖整个页面 */
+  font-size: 18px;
+  color: #c0c4cc;
+  background-color: #f5f5f5;
+}
+
+</style>

+ 222 - 0
src/views/his/user/userCoursePeriodDetails.vue

@@ -0,0 +1,222 @@
+<template>
+  <div class="statistics-container">
+    <!-- 营期课程选择 -->
+    <div class="fixed-header">
+      <el-form :inline="true" :model="queryParams" class="demo-form-inline">
+        <el-form-item label="营期课程">
+          <el-select
+            v-model="queryParams.periodId"
+            placeholder="请选择营期课程"
+            style="width: 400px"
+            clearable
+          >
+            <el-option
+              v-for="item in courseOptions"
+              :key="item.periodId"
+              :label="item.periodName"
+              :value="item.periodId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">查询</el-button>
+        </el-form-item>
+      </el-form>
+
+      <!-- 统计数据展示 -->
+      <el-row :gutter="20" class="statistics-row">
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">到课数</div>
+            <div class="statistics-value">{{ statistics.courseCompleteNum || 0 }}次</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">总学习时长</div>
+            <div class="statistics-value">{{ statistics.courseWatchNum || 0 }}分钟</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">正确答题数</div>
+            <div class="statistics-value">{{ statistics.correctAnswerNum || 0 }}次</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">获得红包数</div>
+            <div class="statistics-value">{{ statistics.redPacketCount || 0 }}次</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">红包总金额</div>
+            <div class="statistics-value">{{ statistics.redPacketAmount || 0 }}元</div>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+
+  </div>
+</template>
+
+<script>
+import {getDays, periodCourseStatisticCount,periodList} from "@/api/course/userCoursePeriod";
+
+export default {
+  name: "userCoursePeriodDetails",
+  props: {
+    periodId: {
+      type: [String, Number],
+      default: ''
+    },
+    active: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: false,
+      // 总条数
+      total: 0,
+      // 课程选项
+      courseOptions: [],
+      // 统计数据
+      statistics: {
+        courseCompleteNum: 0,
+        courseWatchNum: 0,
+        redPacketCount: 0,
+        correctAnswerNum: 0,
+        redPacketAmount: 0
+      },
+      // 列表数据
+      list: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        // videoId: '',
+        periodId: '',
+        trainingCampId:null,
+        userId:null,
+      },
+      // 是否已初始化
+      initialized: false
+    };
+  },
+  created() {
+  },
+  methods: {
+    getDetails(camp,userid) {
+      this.queryParams.trainingCampId = camp.trainingCampId;
+      this.camp = camp;
+      this.userId=userid;
+      this.queryParams.userId=userid;
+      this.user=null;
+      this.courseOptions = []
+      this.queryParams.periodId = null;
+      this.getCourseOptions();
+      this.calculateTotalStatistics();
+    },
+    /** 查询按钮操作 */
+    handleQuery() {
+      this.calculateTotalStatistics();
+    },
+    /** 获取课程选项 */
+    getCourseOptions() {
+
+      this.loading = true;
+      periodList(this.queryParams).then(r => {
+        if (r.code === 200) {
+          this.courseOptions = r.data;
+          this.loading = false;
+        } else {
+          this.$message.error(r.msg || '获取数据失败');
+        }
+        this.loading = false;
+      }).catch(() => {
+        this.loading = false;
+      });
+    },
+
+    /** 计算总统计数据 */
+    calculateTotalStatistics() {
+      console.log("this.queryParams:",this.queryParams)
+      periodCourseStatisticCount(this.queryParams).then(response => {
+        if (response.code === 200) {
+          // 设置列表数据
+          this.statistics.courseCompleteNum = response.data.courseCompleteNum || 0;
+          this.statistics.courseWatchNum = response.data.courseWatchNum || 0;
+          this.statistics.courseWatchTimes = response.data.courseWatchTimes || 0;
+          this.statistics.redPacketCount = response.data.redPacketCount || 0;
+          this.statistics.correctAnswerNum = response.data.correctAnswerNum || 0;
+          this.statistics.redPacketAmount = response.data.redPacketAmount || 0;
+
+        } else {
+          this.$message.error(response.msg || '获取数据失败');
+        }
+        this.loading = false;
+      }).catch(error => {
+        console.error('获取数据失败:', error);
+        this.$message.error('获取数据失败');
+        this.loading = false;
+      });
+
+    }
+  }
+};
+</script>
+
+<style scoped>
+.statistics-container {
+  height: 100%;
+  overflow: hidden;
+  position: relative;
+}
+
+.fixed-header {
+  position: sticky;
+  top: 0;
+  z-index: 10;
+  background-color: #fff;
+  padding: 10px 0;
+  border-bottom: 1px solid #EBEEF5;
+}
+/* 覆盖原有的pagination-container样式 */
+:deep(.pagination-container) {
+  height: auto !important;
+  margin-bottom: 0 !important;
+  margin-top: 0 !important;
+  padding: 0 !important;
+}
+
+.statistics-row {
+  margin: 20px 0;
+}
+
+.statistics-item {
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  padding: 15px;
+  text-align: center;
+  height: 100px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.statistics-title {
+  font-size: 14px;
+  color: #606266;
+  margin-bottom: 10px;
+}
+
+.statistics-value {
+  font-size: 20px;
+  font-weight: bold;
+  color: #303133;
+}
+</style>

+ 57 - 0
src/views/his/user/userCourseStatic.vue

@@ -0,0 +1,57 @@
+<template>
+  <el-tabs v-model="activeName" type="card" @tab-click="handleClick">
+    <el-tab-pane label="数据总览" name="first"><userStaticAll  ref="userStaticAll" /></el-tab-pane>
+    <el-tab-pane label="课节详情" name="second"><userCoursePeriodDetails  ref="userCoursePeriodDetails" /></el-tab-pane>
+    <el-tab-pane label="转化记录" name="third"><userCourseConversionRecord  ref="userCourseConversionRecord" /></el-tab-pane>
+
+  </el-tabs>
+</template>
+<script>
+import userStaticAll from './userStaticAll.vue';
+import userCoursePeriodDetails from './userCoursePeriodDetails.vue';
+import userCourseConversionRecord from './userCourseConversionRecord.vue';
+
+export default {
+  name: "userCourseStatic",
+  components: {
+    userStaticAll,userCoursePeriodDetails,userCourseConversionRecord
+  },
+  data() {
+
+    return {
+      camp:{},
+      userId:null,
+      activeName: 'first'
+    };
+  },
+  methods: {
+    handleClick(tab, event) {
+      if (tab.name === "first"){
+        setTimeout(() => {
+          this.$refs.userStaticAll.getDetails(this.camp,this.userId);
+        }, 1);
+      }else if (tab.name === "second"){
+        setTimeout(() => {
+          this.$refs.userCoursePeriodDetails.getDetails(this.camp,this.userId);
+        }, 1);
+      }else {
+        setTimeout(() => {
+          this.$refs.userCourseConversionRecord.getDetails(this.userId);
+        }, 1);
+      }
+
+    },
+
+    getDetails(camp,userid) {
+      this.camp = camp;
+      this.userId=userid;
+      this.activeName = 'first';
+      // 默认加载“数据总览”接口
+      this.$nextTick(() => {
+        this.$refs.userStaticAll.getDetails(this.camp, this.userId);
+      });
+
+    },
+  }
+};
+</script>

+ 71 - 0
src/views/his/user/userDetails.vue

@@ -0,0 +1,71 @@
+<template>
+  <div>
+    <div style="background-color: #f0f2f5; padding-bottom: 20px; min-height: 100%; " >
+      <div style="padding: 20px; background-color: #fff;">
+        会员详情
+      </div>
+    </div>
+    <template>
+      <el-tabs v-model="activeName"  :tab-position="tabPosition" style="height: 200px;margin: 40px">
+        <el-tab-pane label="基本信息" name="basic"><userDetails  ref="userDetails" /></el-tab-pane>
+        <el-tab-pane label="行为轨迹" name="behavior"><userBehavior  ref="userBehavior" /></el-tab-pane>
+        <el-tab-pane label="训练营"  name="course"><userCoursePeriod  ref="userCoursePeriod" /></el-tab-pane>
+      </el-tabs>
+    </template>
+  </div>
+
+</template>
+
+
+
+<script>
+import userDetails from '../../components/his/userDetails.vue';
+import userBehavior from './userBehavior.vue';
+import userCoursePeriod from './userCoursePeriod.vue';
+
+export default {
+  name: "userDetailsByNew",
+  props:["data"],
+  components: { userDetails,userBehavior,userCoursePeriod},
+  data() {
+    return {
+      activeName: 'basic',
+      // 左侧遮罩层
+      leftLoading: true,
+      // 左侧查询参数
+      leftQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        hasNextPage: false,
+        scs: 'order_number(desc),training_camp_id(desc)',
+        trainingCampName: null,
+        userId:null,
+      },
+      tabPosition: 'top',
+    }
+  },
+  created() {
+
+  },
+  methods: {
+    handleClick(tab, event) {
+      console.log(tab, event);
+    },
+    getDetails(userId) {
+      this. activeName='basic';
+      setTimeout(() => {
+        this.$refs.userDetails.getDetails(userId);
+      }, 1);
+      setTimeout(() => {
+        this.$refs.userBehavior.getDetails(userId);
+      }, 1);
+      setTimeout(() => {
+        this.$refs.userCoursePeriod.getDetails(userId);
+      }, 1);
+    },
+  }
+}
+</script>
+<style>
+
+</style>

+ 249 - 0
src/views/his/user/userStaticAll.vue

@@ -0,0 +1,249 @@
+<template>
+  <div class="statistics-container">
+    <!-- 营期课程选择 -->
+    <div class="fixed-header">
+      <!-- 统计数据展示 -->
+      <el-row :gutter="20" class="statistics-row">
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">到课数</div>
+            <div class="statistics-value">{{ statistics.courseCompleteNum || 0 }}次</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">总学习时长</div>
+            <div class="statistics-value">{{ statistics.courseWatchNum || 0 }}分钟</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">正确答题数</div>
+            <div class="statistics-value">{{ statistics.correctAnswerNum || 0 }}次</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">获得红包数</div>
+            <div class="statistics-value">{{ statistics.redPacketCount || 0 }}次</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">红包总金额</div>
+            <div class="statistics-value">{{ statistics.redPacketAmount || 0 }}元</div>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+
+    <!-- 列表统计展示 -->
+    <div class="table-wrapper">
+      <el-table v-loading="loading" :data="list">
+        <el-table-column type="index" label="序号" width="50" align="center" fixed/>
+        <el-table-column prop="courseName" label="课程名称" align="center" min-width="100" fixed/>
+        <el-table-column prop="videoName" label="课节名称" align="center" min-width="100" fixed/>
+        <el-table-column label="记录类型" align="center" prop="logType">
+          <template slot-scope="scope">
+            <el-tag prop="type" v-for="(item, index) in typeOptions" v-if="scope.row.logType==item.dictValue">{{item.dictLabel}}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="duration" label="播放时长" align="center" min-width="100"/>
+        <el-table-column prop="finishTime" label="完课时间" align="center" min-width="120"/>
+        <el-table-column prop="lastHeartbeatTime" label="最后心跳时间" align="center" min-width="120"/>
+      </el-table>
+
+      <!-- 分页 -->
+      <div class="custom-pagination-container">
+        <pagination
+          v-show="total > 0"
+          :total="total"
+          :page.sync="queryParams.pageNum"
+          :limit.sync="queryParams.pageSize"
+          @pagination="getCountList"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {periodCourseStatisticCount} from "@/api/course/userCoursePeriod";
+import { listCourseWatchLog } from "@/api/course/qw/courseWatchLog";
+
+
+export default {
+  name: "userStaticAll",
+  props: {
+    periodId: {
+      type: [String, Number],
+      default: ''
+    },
+    active: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      typeOptions: [],
+      // 遮罩层
+      loading: false,
+      // 总条数
+      total: 0,
+      // 统计数据
+      statistics: {
+        courseCompleteNum: 0,
+        courseWatchNum: 0,
+        redPacketCount: 0,
+        correctAnswerNum: 0,
+        redPacketAmount: 0
+      },
+      // 列表数据
+      list: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        // videoId: '',
+        periodId: '',
+        trainingCampId:null,
+        userId:null,
+      },
+    };
+  },
+  created() {
+    this.getDicts("sys_course_watch_log_type").then((response) => {
+      this.typeOptions = response.data;
+    });
+  },
+  methods: {
+    getDetails(camp,userid) {
+    this.queryParams.trainingCampId = camp.trainingCampId;
+      this.camp = camp;
+      this.userId=userid;
+      this.queryParams.userId=userid;
+      this.user=null;
+      if (this.queryParams.trainingCampId){
+        this.getCountList();
+        this.calculateTotalStatistics();
+      }
+
+    },
+    /** 获取列表数据 */
+    getCountList() {
+      this.loading = true;
+      if (!this.queryParams.trainingCampId || !this.queryParams.userId){
+        return
+      }
+      listCourseWatchLog(this.queryParams).then(response => {
+        if (response.code === 200) {
+          // 设置列表数据
+          this.list = response.rows;
+          this.total = response.total || 0;
+
+
+        } else {
+          this.$message.error(response.msg || '获取数据失败');
+        }
+        this.loading = false;
+      }).catch(error => {
+        console.error('获取数据失败:', error);
+        this.$message.error('获取数据失败');
+        this.loading = false;
+      });
+    },
+    /** 计算总统计数据 */
+    calculateTotalStatistics() {
+
+      console.log("this.queryParams:",this.queryParams)
+      periodCourseStatisticCount(this.queryParams).then(response => {
+        if (response.code === 200) {
+          // 设置列表数据
+          this.statistics.courseCompleteNum = response.data.courseCompleteNum || 0;
+          this.statistics.courseWatchNum = response.data.courseWatchNum || 0;
+          this.statistics.courseWatchTimes = response.data.courseWatchTimes || 0;
+          this.statistics.redPacketCount = response.data.redPacketCount || 0;
+          this.statistics.correctAnswerNum = response.data.correctAnswerNum || 0;
+          this.statistics.redPacketAmount = response.data.redPacketAmount || 0;
+
+        } else {
+          this.$message.error(response.msg || '获取数据失败');
+        }
+        this.loading = false;
+      }).catch(error => {
+        console.error('获取数据失败:', error);
+        this.$message.error('获取数据失败');
+        this.loading = false;
+      });
+
+    }
+  }
+};
+</script>
+
+<style scoped>
+.statistics-container {
+  height: 100%;
+  overflow: hidden;
+  position: relative;
+}
+
+.fixed-header {
+  position: sticky;
+  top: 0;
+  z-index: 10;
+  background-color: #fff;
+  padding: 10px 0;
+  border-bottom: 1px solid #EBEEF5;
+}
+
+.table-wrapper {
+  height: calc(100% - 220px);
+  overflow: visible;
+  position: relative;
+}
+
+.custom-pagination-container {
+  padding: 10px 0 12px 0;
+  text-align: right;
+  background-color: #fff;
+  position: relative;
+  z-index: 1;
+}
+
+/* 覆盖原有的pagination-container样式 */
+:deep(.pagination-container) {
+  height: auto !important;
+  margin-bottom: 0 !important;
+  margin-top: 0 !important;
+  padding: 0 !important;
+}
+
+.statistics-row {
+  margin: 20px 0;
+}
+
+.statistics-item {
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  padding: 15px;
+  text-align: center;
+  height: 100px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.statistics-title {
+  font-size: 14px;
+  color: #606266;
+  margin-bottom: 10px;
+}
+
+.statistics-value {
+  font-size: 20px;
+  font-weight: bold;
+  color: #303133;
+}
+</style>

+ 15 - 3
src/views/qw/sopTemp/index.vue

@@ -263,7 +263,7 @@
             value-format="HH:mm"
             format="HH:mm"
             placeholder="时间"
-            style="width: 100px;height: 20px;margin-left: 10px;margin-top: 10px">
+            style="width: 200px;height: 20px;margin-left: 10px;margin-top: 10px">
           </el-time-picker>
         </el-form-item>
         <el-form-item label="每天催课次数" prop="num" v-if="form.sendType == 11 && !form.id">
@@ -278,7 +278,7 @@
             format="HH:mm"
             :picker-options="{ selectableRange: startTimeRange }"
             placeholder="时间"
-            style="width: 100px;height: 20px;margin-left: 10px;margin-top: 10px">
+            style="width: 200px;height: 20px;margin-left: 10px;margin-top: 10px">
           </el-time-picker>
         </el-form-item>
       </el-form>
@@ -403,10 +403,22 @@ export default {
         gap: [
           {required: true, message: '间隔天数不能为空', trigger: 'blur'}
         ],
+        time:[{
+          required: true, message: '发课时间不能为空', trigger: 'blur'
+        }],
+        courseId:[{
+          required: true, message: '课程不能为空', trigger: 'blur'
+        }],
+        companyId:[{
+          required: true, message: '销售公司不能为空', trigger: 'blur'
+        }],
+        project:[{
+          required: true, message: '所属项目不能为空', trigger: 'blur'
+        }],
       },
       contentRules: {
         time: [{required: true, message: '时间不能为空', trigger: 'blur'}],
-      }
+      },
     };
   },
   created() {

+ 17 - 4
src/views/qw/sopTemp/updateSopTemp.vue

@@ -1219,8 +1219,18 @@ export default {
       if (this.setting[index].content.length > 0 && this.form.sendType == 1) {
         return this.$message.error("因为企微接口限制,企微模板一天只能设置一条消息~")
       } else {
-        this.setting[index].content.push({type: this.defaultContentType, contentType: 1, setting: [{contentType: '1', value: "",}]})
-        this.videoList.push([])
+        const sourceContent = this.setting[index].content[0];
+        const newContent = {
+          type: this.defaultContentType,
+          contentType: 1,
+          setting: [{contentType: '1', value: ""}],
+          // 复制课程相关字段,如 courseId、videoId,根据实际数据结构补充
+          courseId: sourceContent.courseId,
+          videoId: sourceContent.videoId,
+          courseType: sourceContent.courseType
+        };
+        this.setting[index].content.push(newContent);
+        this.videoList.push([]);
         this.addTag.push({
           addTag: [],
           inputVisible: false,
@@ -1228,9 +1238,12 @@ export default {
           delTag: [],
           delTagVisible: false,
           delTagValue: ''
-        })
+        });
+        // 如果有视频列表关联,也可同步处理 videoList,根据 courseId 重新请求或复制已有视频数据
+        if (sourceContent.courseId) {
+          this.courseUpdate(newContent, index, this.setting[index].content.length - 1);
+        }
       }
-
     },
     delContent(index, contentIndex) {
       this.setting[index].content.splice(contentIndex, 1)

+ 7 - 2
src/views/system/config/config.vue

@@ -963,6 +963,11 @@
                <el-radio label="1">线路二</el-radio>
              </el-radio-group>
            </el-form-item>
+           <el-form-item label="一级域名">
+             <el-tooltip class="item" effect="dark" content="顶级域名" placement="top-end">
+               <el-input  v-model="form18.courseDomainName"     ></el-input>
+             </el-tooltip>
+           </el-form-item>
            <el-form-item label="通用看课域名">
              <el-tooltip class="item" effect="dark" content="真链域名" placement="top-end">
                <el-input  v-model="form18.realLinkDomainName"     ></el-input>
@@ -1088,8 +1093,8 @@
                  v-model="scope.row.type"
                   placeholder="请选择类型"
                  :disabled="!scope.row.editing">
-                 <el-option label="公众号" value="1"></el-option>
-                 <el-option label="小程序" value="2"></el-option>
+                 <el-option label="小程序" value="1"></el-option>
+                 <el-option label="服务号" value="2"></el-option>
                </el-select>
              </template>
            </el-table-column>