浏览代码

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_adminUI

caoliqin 1 周之前
父节点
当前提交
1494c8ff87

+ 2 - 2
.env.prod-hcl

@@ -3,9 +3,9 @@ VUE_APP_TITLE =恒春来管理系统
 # 首页菜单标题
 VUE_APP_TITLE_INDEX =恒春来
 # 公司名称
-VUE_APP_COMPANY_NAME =黑龙江珠斯网络科技有限公司
+VUE_APP_COMPANY_NAME =福州恒春来健康咨询有限公司
 # ICP备案号
-VUE_APP_ICP_RECORD =黑ICP备2024024871号-3
+VUE_APP_ICP_RECORD =闽ICP备2024061323号
 # ICP网站访问地址
 VUE_APP_ICP_URL =https://beian.miit.gov.cn
 # 网站LOG

+ 8 - 0
src/api/course/userCourseComplaintRecord.js

@@ -16,6 +16,14 @@ export function getUserCourseComplaintRecord(recordId) {
     method: 'get'
   })
 }
+//查询看课投诉记录详细-客户所属关系
+export function getUserCourseComplaintRecordByUserId(userId) {
+  return request({
+    url: '/course/userCourseComplaintRecord/getInfoByUserId/' + userId,
+    method: 'get'
+  })
+}
+
 //
 // // 新增看课投诉记录
 // export function addUserCourseComplaintRecord(data) {

+ 1 - 1
src/api/course/userCoursePublic.js

@@ -29,7 +29,7 @@ export function addUserCourse(data) {
 // 修改课程
 export function updateUserCourse(data) {
   return request({
-    url: '/course/userCourse/public',
+    url: '/course/userCourse',
     method: 'put',
     data: data
   })

+ 25 - 0
src/api/his/user.js

@@ -90,3 +90,28 @@ export function enabledUsers(data) {
     data: data
   })
 }
+
+// 添加积分
+export function addPoint(data) {
+  return request({
+    url: '/his/user/addPoints',
+    method: 'post',
+    data: data
+  })
+}
+
+export function listUserByProject(query) {
+  return request({
+    url: '/his/user/listProject',
+    method: 'get',
+    params: query
+  })
+}
+
+// 删除用户
+export function delUserCompanyUser(id) {
+  return request({
+    url: '/his/user/delete/' + id,
+    method: 'delete'
+  })
+}

+ 1 - 0
src/utils/cos.js

@@ -29,6 +29,7 @@ export const uploadObject = async (file,onProgress,type,callBackUp) => {
 
         // 初始化
         const cos = new COS({
+            Timeout: 1200 * 1000,
             getAuthorization: (options, callback) => {
                 callback({
                     TmpSecretId: credentials.tmpSecretId,

+ 1 - 0
src/views/components/his/integralGoodsDetails.vue

@@ -17,6 +17,7 @@
 
 
           <el-descriptions-item label="所需积分"><span v-if="item!=null">{{item.integral}}</span></el-descriptions-item>
+          <el-descriptions-item label="需支付金额"><span v-if="item!=null">{{item.cash}}</span></el-descriptions-item>
           <el-descriptions-item label="排序"><span v-if="item!=null">{{item.sort}}</span></el-descriptions-item>
           <el-descriptions-item label="库存"><span v-if="item!=null">{{item.stock}}</span></el-descriptions-item>
           <el-descriptions-item label="状态"><span v-if="item!=null"> <dict-tag :options="statusOptions" :value="item.status"/></span></el-descriptions-item>

+ 8 - 2
src/views/components/his/integralOrderDetails.vue

@@ -5,8 +5,9 @@
       </div>
 <div class="contentx" v-if="item!=null">
         <div class="desct"> 积分订单信息</div>
-        <div class="order-status" v-if="item!=null" >
-                  <el-steps  :active="item.status==3?item.status:item.status-1" align-center finish-status="success">
+        <div class="order-status" v-if="item!=null && item.status != 5" >
+                  <el-steps  :active="item.status==4?0:item.status" align-center finish-status="success">
+                    <el-step title="待支付"></el-step>
                     <el-step title="待发货"></el-step>
                     <el-step title="待收货"></el-step>
                     <el-step title="已完成"></el-step>
@@ -76,6 +77,11 @@
                   <p>¥{{scope.row.integral}}</p>
                 </template>
               </el-table-column>
+              <el-table-column label="金额" align="center">
+               <template slot-scope="scope">
+                 <p>¥{{scope.row.cash || 0}}</p>
+               </template>
+              </el-table-column>
 
             </el-table>
        </div>

+ 0 - 3
src/views/course/userCourse/index.vue

@@ -234,9 +234,6 @@
             </el-form-item>
           </el-col>
         </el-row>
-        <el-form-item label="排序" prop="sort">
-          <el-input-number v-model="form.sort"  :min="0" label="请输入排序"></el-input-number>
-        </el-form-item>
         <el-form-item label="课程封面" prop="imgUrl">
           <ImageUpload v-model="form.imgUrl" type="image" :num="10" :width="150" :height="150"/>
         </el-form-item>

+ 3 - 0
src/views/course/userCourse/public.vue

@@ -548,6 +548,9 @@ export default {
         imgUrl: [
           {required: true, message: "封面图片不能为空", trigger: "blur"}
         ],
+        talentId: [
+          {required: true, message: "关联达人不能为空", trigger: "blur"}
+        ],
         isTui: [
           {required: true, message: "是否推荐不能为空", trigger: "blur"}
         ],

+ 51 - 1
src/views/course/userCourseComplaintRecord/index.vue

@@ -116,6 +116,13 @@
             @click="handleDetails(scope.row)"
             v-hasPermi="['course:userCourseComplaintRecord:edit']"
           >详情</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-info"
+            @click="handleDetailsQw(scope.row)"
+            v-hasPermi="['course:userCourseComplaintRecord:queryQw']"
+          >客户所属详情</el-button>
           <el-button
             size="mini"
             type="text"
@@ -179,11 +186,41 @@
         <el-button @click="cancel">关 闭</el-button>
       </div>
     </el-dialog>
+
+    <el-dialog :title="title" :visible.sync="openQw"  width="1300px" append-to-body>
+      <el-table border :data="formQwList">
+        <el-table-column label="员工账号" align="center" prop="userId" />
+        <el-table-column label="员工名称" align="center" prop="qwUserName" />
+        <el-table-column label="销售账号" align="center" prop="userName" />
+        <el-table-column label="销售昵称" align="center" prop="nickName" />
+        <el-table-column label="客户名称" align="center" prop="name" />
+        <el-table-column label="头像" align="center" prop="avatar">
+          <template slot-scope="scope">
+            <el-popover
+              placement="right"
+              title=""
+              trigger="hover"
+            >
+              <img slot="reference" :src="scope.row.avatar" width="50" >
+              <img :src="scope.row.avatar" style="max-width: 120px;">
+            </el-popover>
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" align="center" prop="remark" />
+        <el-table-column label="描述信息" align="center" prop="description" />
+      </el-table>
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import { listUserCourseComplaintRecord, delUserCourseComplaintRecord, exportUserCourseComplaintRecord, getUserCourseComplaintRecord } from "@/api/course/userCourseComplaintRecord";
+import {
+  listUserCourseComplaintRecord,
+  delUserCourseComplaintRecord,
+  exportUserCourseComplaintRecord,
+  getUserCourseComplaintRecord,
+  getUserCourseComplaintRecordByUserId
+} from '@/api/course/userCourseComplaintRecord'
 import { addBlack } from "@/api/course/courseWatchComment";
 export default {
   name: "UserCourseComplaintRecord",
@@ -209,6 +246,7 @@ export default {
       title: "",
       // 是否显示弹出层
       open: false,
+      openQw: false,
       // 图片列表
       imageList: [],
       // 日期范围
@@ -229,6 +267,7 @@ export default {
       },
       // 表单参数
       form: {},
+      formQwList:[],
       // 表单校验
       rules: {
       }
@@ -321,6 +360,17 @@ export default {
         this.msgError("获取详情失败,请稍后重试");
       });
     },
+
+    handleDetailsQw(row){
+      this.reset();
+      getUserCourseComplaintRecordByUserId(row.userId).then(response => {
+        this.formQwList = response.data;
+        this.openQw = true;
+        this.title = "客户所属详情";
+      }).catch(error => {
+        this.msgError("获取客户所属详情失败,请稍后重试");
+      })
+    },
     /** 预览图片 */
     previewImage(index) {
       // Element UI的图片组件会自动处理预览

+ 5 - 88
src/views/course/userTalent/index.vue

@@ -19,28 +19,6 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="所属公司" prop="companyIds" >
-        <el-select
-          v-model="companyIds"
-          filterable
-          multiple
-          remote
-          reserve-keyword
-          placeholder="请输入关键字搜索"
-          :remote-method="fetchCompanies"
-          :loading="loadingCompanies"
-          size="small"
-          @change="formatCompanies"
-          style="width: 180px"
-        >
-          <el-option
-            v-for="company in companyOptions"
-            :key="company.companyId"
-            :label="company.companyName"
-            :value="company.companyId"
-          />
-        </el-select>
-      </el-form-item>
 
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@@ -173,15 +151,15 @@
     />
 
     <!-- 添加或修改达人对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="1000px" append-to-body @close="handleClose">>
+    <el-dialog :title="title" :visible.sync="open" width="1000px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-        <el-row :gutter="20">
-          <el-col :span="12">
+        <el-row>
+          <el-col :span="8">
             <el-form-item label="昵称" prop="nickName">
               <el-input v-model="form.nickName" placeholder="请输入昵称" />
             </el-form-item>
           </el-col>
-          <el-col :span="12">
+          <el-col :span="8">
             <el-form-item label="性别" prop="sex">
               <el-select v-model="form.sex" placeholder="性别" clearable size="small">
                 <el-option
@@ -193,9 +171,7 @@
               </el-select>
             </el-form-item>
           </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
+          <el-col :span="8">
             <el-form-item label="关联用户" prop="userId" >
               <el-select v-model="form.userId" remote filterable reserve-keyword placeholder="输入手机号搜索" @change="selectUser" :remote-method="userMethod" >
                 <el-option
@@ -210,25 +186,6 @@
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="关联公司" prop="companyId" >
-              <el-select v-model="form.companyId"
-                         remote
-                         filterable
-                         reserve-keyword
-                         placeholder="输入公司名称搜索"
-                         :remote-method="fetchCompanies" >
-                <el-option
-                  v-for="item in companyOptions"
-                  :key="item.companyId"
-                  :label="item.companyId"
-                  :value="item.companyId">
-                  <span style="float: left">{{ item.companyId }}</span>
-                  <span style="margin-left: 30px ;">{{item.companyName}}</span>
-                </el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
         </el-row>
 
         <el-form-item label="头像" prop="avatar">
@@ -313,7 +270,6 @@ import {
   updateUserTalent,
   exportUserTalent,
   auditUserTalent,
-  getCompanies
 } from '@/api/course/userTalent'
 import { listBySearch} from "@/api/his/user";
 import ImageUpload from '@/components/ImageUpload/index';
@@ -354,8 +310,6 @@ export default {
       },
       userList:[],
       tagsOptions:[],
-      companyOptions:[],
-      loadingCompanies: false, // 加载状态
       tags:[],
       sexOptions:[],
       // 遮罩层
@@ -394,10 +348,7 @@ export default {
         likes: null,
         isDel: null,
         isAudit:0,
-        //销售公司
-        companyIdStr: null
       },
-      companyIds: [],
       // 表单参数
       form: {},
       // 表单校验
@@ -417,9 +368,6 @@ export default {
         title: [
           { required: true, message: "标题不能为空", trigger: "blur" }
         ],
-        companyId: [
-          { required: true, message: "关联公司不能为空", trigger: "change" }
-        ]
       }
     };
   },
@@ -439,27 +387,6 @@ export default {
     handleVideoDuration(duration) {
       this.form.videoDuration = duration;
     },
-    async fetchCompanies(query) {
-      if (!query) {
-        this.companyOptions = [];
-        return;
-      }
-      this.loadingCompanies = true;
-      try {
-        getCompanies(query).then(response => {
-          this.companyOptions = response.data;
-        })
-      } catch (err) {
-        console.error('查询销售公司失败:', err);
-        this.companyOptions = [];
-      } finally {
-        this.loadingCompanies = false;
-      }
-    },
-    formatCompanies(){
-      if(this.companyIds)
-      this.queryParams.companyIdStr = this.companyIds.join(',');
-    },
     submitAuditForm(){
       this.$refs["auditForm"].validate(valid => {
         if(valid){
@@ -545,14 +472,8 @@ export default {
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm("queryForm");
-      this.resetCompany();
       this.handleQuery();
     },
-    resetCompany() {
-      this.companyOptions =  [];
-      this.companyIds = [];
-      this.queryParams.companyIdStr=null;
-    },
     // 多选框选中数据
     handleSelectionChange(selection) {
       this.ids = selection.map(item => item.talentId)
@@ -581,10 +502,6 @@ export default {
         this.title = "修改达人";
       });
     },
-    /**修改页面关闭方法*/
-    handleClose(){
-        this.resetCompany();
-    },
     /** 提交按钮 */
     submitForm() {
       this.$refs["form"].validate(valid => {

+ 767 - 90
src/views/course/videoResource/index.vue

@@ -277,11 +277,40 @@
               </span>
 
               <div v-if="file.status === 'uploading'" class="el-upload-list__item-progress">
-                <el-progress
-                  :percentage="file.percentage"
-                  :show-text="false"
-                  :width="52"
-                ></el-progress>
+                <div class="dual-upload-progress">
+                  <div class="total-progress">
+                    <el-progress
+                      :percentage="currentUploadProgress.total"
+                      :show-text="true"
+                      :width="52"
+                      :format="() => `${Math.round(currentUploadProgress.total)}%`"
+                    ></el-progress>
+                  </div>
+                  <div class="line-progress-container">
+                    <div class="line-progress-item">
+                      <span class="line-label">线路1:</span>
+                      <el-progress
+                        :percentage="currentUploadProgress.line1"
+                        :show-text="false"
+                        :width="30"
+                        :status="currentUploadProgress.line1Status === 'success' ? 'success' :
+                                currentUploadProgress.line1Status === 'failed' ? 'exception' : ''"
+                      ></el-progress>
+                      <span class="line-status-text">{{ Math.round(currentUploadProgress.line1) }}%</span>
+                    </div>
+                    <div class="line-progress-item">
+                      <span class="line-label">线路2:</span>
+                      <el-progress
+                        :percentage="currentUploadProgress.line2"
+                        :show-text="false"
+                        :width="30"
+                        :status="currentUploadProgress.line2Status === 'success' ? 'success' :
+                                currentUploadProgress.line2Status === 'failed' ? 'exception' : ''"
+                      ></el-progress>
+                      <span class="line-status-text">{{ Math.round(currentUploadProgress.line2) }}%</span>
+                    </div>
+                  </div>
+                </div>
               </div>
             </div>
           </el-upload>
@@ -293,7 +322,9 @@
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button @click="cancel">取消</el-button>
-        <el-button type="primary" @click="submitForm">保存</el-button>
+        <el-button type="primary" @click="submitForm" :disabled="isUploading">
+          {{ isUploading ? '上传中...' : '保存' }}
+        </el-button>
       </div>
     </minimizable-dialog>
 
@@ -376,10 +407,45 @@
             {{ formatDuration(scope.row.duration) }}
           </template>
         </el-table-column>
-        <el-table-column label="上传进度" align="center" width="120">
+        <el-table-column label="上传进度" align="center" width="200">
           <template slot-scope="scope">
-            <el-progress :percentage="scope.row.progress" :status="scope.row.progress === 100 ? 'success' : undefined"></el-progress>
+            <div class="batch-upload-progress">
+              <div class="total-progress-row">
+                <span class="progress-label">总进度:</span>
+                <el-progress
+                  :percentage="scope.row.progress || 0"
+                  :status="getProgressStatus(scope.row)"
+                  :show-text="true"
+                  :format="() => `${Math.round(scope.row.progress || 0)}%`"
+                ></el-progress>
+              </div>
+              <div v-if="scope.row.uploadDetails" class="line-progress-rows">
+                <div class="line-progress-row">
+                  <span class="line-label">线路1:</span>
+                  <el-progress
+                    :percentage="scope.row.uploadDetails.line1 || 0"
+                    :status="scope.row.uploadDetails.line1Status === 'success' ? 'success' :
+                            scope.row.uploadDetails.line1Status === 'failed' ? 'exception' : 'warning'"
+                    :show-text="false"
+                    style="width: 60px;"
+                  ></el-progress>
+                  <span class="line-percentage">{{ Math.round(scope.row.uploadDetails.line1 || 0) }}%</span>
+                </div>
+                <div class="line-progress-row">
+                  <span class="line-label">线路2:</span>
+                  <el-progress
+                    :percentage="scope.row.uploadDetails.line2 || 0"
+                    :status="scope.row.uploadDetails.line2Status === 'success' ? 'success' :
+                            scope.row.uploadDetails.line2Status === 'failed' ? 'exception' : 'warning'"
+                    :show-text="false"
+                    style="width: 60px;"
+                  ></el-progress>
+                  <span class="line-percentage">{{ Math.round(scope.row.uploadDetails.line2 || 0) }}%</span>
+                </div>
+              </div>
+            </div>
           </template>
+
         </el-table-column>
         <el-table-column label="操作" align="center" width="150">
           <template slot-scope="scope">
@@ -393,6 +459,13 @@
               type="text"
               icon="el-icon-delete"
               @click="handleDeleteVideo(scope.row)">删除</el-button>
+            <el-button
+              v-if="scope.row.progress < 100 && scope.row.uploadStatus === 'failed'"
+              size="mini"
+              type="text"
+              icon="el-icon-refresh"
+              @click="retryBatchUpload(scope.row)"
+              style="color: #E6A23C;">重试</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -706,7 +779,16 @@ export default {
         typeId: null,
         typeSubId: null,
         projectIds: [],
-        sort: null
+        sort: null,
+        // 新增上传状态字段
+        uploadStatus: 'pending', // pending, uploading, success, failed
+        uploadProgress: {
+          total: 0,
+          line1: 0,
+          line2: 0,
+          line1Status: 'pending', // pending, uploading, success, failed
+          line2Status: 'pending'
+        }
       },
       // 表单校验
       rules: {
@@ -792,6 +874,17 @@ export default {
       },
       // 是否存在最小化窗口
       hasMinimizableDialog: false,
+      // 新增上传相关状态
+      isUploading: false,
+      currentUploadProgress: {
+        total: 0,
+        line1: 0,
+        line2: 0,
+        line1Status: 'pending',
+        line2Status: 'pending'
+      },
+      // 上传任务队列
+      uploadQueue: []
     }
   },
   watch: {
@@ -1047,16 +1140,22 @@ export default {
     //获取第一帧封面
     async getFirstThumbnail(file, form){
       try {
-        const uniqueFileName = `clipped_${Date.now()}_${Math.random().toString(36).slice(2, 8)}.mp4`;
-
         //截取小文件
+        // 打印原始文件名和大小
+        console.log("原始文件名:", file.name);
+        console.log("原始文件大小:", file.size, "bytes");
+        console.log("原始文件类型:", file.type);
+
+        // 截取小文件
         const clippedBlob = await this.clipVideoFirstTwoSeconds(file);
+        console.log("clippedBlob:::::::::", clippedBlob);
+        console.log("截取后的Blob大小:", clippedBlob.size, "bytes");
+        console.log("截取后的Blob类型:", clippedBlob.type);
 
-        const clippedFile = new File([clippedBlob], uniqueFileName, {
+        const clippedFile = new File([clippedBlob], 'clipped_video.mp4', {
           type: 'video/mp4',
           lastModified: Date.now()
         });
-        console.log("调用请请求---------------》",response)
         // 3. 调用接口获取封面
         const response = await getThumbnail(clippedFile);
         console.log("获取封面请求---------------》",response)
@@ -1065,128 +1164,205 @@ export default {
         console.error('获取封面失败:', error);
       }
     },
+
     //截取大文件视频
-    clipVideoFirstTwoSeconds(file) {
+    async clipVideoFirstTwoSeconds(file) {
       return new Promise((resolve, reject) => {
-        // 创建视频元素用于处理
         const video = document.createElement('video');
         video.src = URL.createObjectURL(file);
-        video.crossOrigin = 'anonymous';
-        video.preload = 'metadata';
-
-        // 视频元数据加载完成后开始处理
-        video.onloadedmetadata = async () => {
-          try {
-            // 计算截取时长
-            const duration = Math.min(2, video.duration);
-
-            // 直接从视频元素捕获流
-            const stream = video.captureStream();
-
-            // 创建 MediaRecorder 录制截取的片段
-            const mediaRecorder = new MediaRecorder(stream);
-            const chunks = [];
-
-            // 收集录制的视频数据
-            mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
-
-            // 录制结束后处理结果
-            mediaRecorder.onstop = () => {
-              // 合并数据为 Blob(MP4 格式)
-              const blob = new Blob(chunks, { type: 'video/mp4' });
-              resolve(blob);
-
-              // 清理资源
-              URL.revokeObjectURL(video.src);
-              stream.getTracks().forEach(track => track.stop());
-            };
-
-            // 开始录制
-            mediaRecorder.start();
-
-            // 播放视频并在指定时间后停止录制
-            video.currentTime = 0; // 从开头开始
-            video.play();
-
-            // 到达截取时长后停止录制
-            setTimeout(() => {
-              video.pause();
-              mediaRecorder.stop();
-            }, duration * 1000); // 转换为毫秒
-
-          } catch (error) {
-            reject(new Error('视频截取失败: ' + error.message));
-          }
+        video.muted = true;
+        video.playsInline = true;
+
+        video.onloadedmetadata = () => {
+          video.currentTime = 0; // 定位到第一帧
+          video.onseeked = () => {
+            const canvas = document.createElement('canvas');
+            canvas.width = video.videoWidth;
+            canvas.height = video.videoHeight;
+            const ctx = canvas.getContext('2d');
+            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
+
+            canvas.toBlob(
+              (blob) => {
+                URL.revokeObjectURL(video.src);
+                resolve(blob); // 返回 JPEG Blob
+              },
+              'image/jpeg',
+              0.8 // 质量
+            );
+          };
         };
 
-        // 视频加载错误处理
         video.onerror = () => {
-          reject(new Error('视频加载失败,请检查文件格式'));
+          URL.revokeObjectURL(video.src);
+          reject(new Error('视频加载失败'));
         };
       });
     },
     //上传腾讯云Pcdn
     async uploadVideoToTxPcdn(file, form, onProgress) {
       try {
+        // 更新线路1状态为上传中
+        this.updateUploadProgress('line1Status', 'uploading');
+
         const data = await uploadObject(file, (progress) => {
+          const progressPercent = Math.floor(progress.percent * 100);
+          this.updateUploadProgress('line1', progressPercent);
+
           const progressEvent = {
-            percent: Math.floor(progress.percent * 100 / 2), // COS SDK 百分比是 0-1,el-upload 需要 0-100
+            percent: progressPercent,
             loaded: progress.loaded,
             total: progress.total,
-            lengthComputable: true // 文件上传通常总大小可知
+            lengthComputable: true
           };
           onProgress(progressEvent);
         }, 1);
 
         let line_1 = `${process.env.VUE_APP_VIDEO_LINE_1}${data.urlPath}`;
-
         form.fileKey = data.urlPath.substring(1);
         form.videoUrl = line_1;
         form.line1 = line_1;
 
+        // 更新线路1状态为成功
+        this.updateUploadProgress('line1Status', 'success');
+        this.updateUploadProgress('line1', 100);
+
         this.$message.success("线路一上传成功");
+        return { success: true, url: line_1 };
       } catch (error) {
+        // 更新线路1状态为失败
+        this.updateUploadProgress('line1Status', 'failed');
         this.$message.error("线路一上传失败");
+        return { success: false, error: error.message };
       }
     },
     //上传华为云Obs
     async uploadVideoToHwObs(file, form, onProgress) {
       try {
+        // 更新线路2状态为上传中
+        this.updateUploadProgress('line2Status', 'uploading');
+
         const data = await uploadToOBS(file, (progress) => {
+          const progressPercent = Math.floor(progress);
+          this.updateUploadProgress('line2', progressPercent);
+
           const progressEvent = {
-            percent: Math.floor(progress / 2) + 50,
+            percent: progressPercent,
             loaded: progress,
             total: progress,
-            lengthComputable: true // 文件上传通常总大小可知
+            lengthComputable: true
           };
           onProgress(progressEvent);
         }, 1);
 
         form.line2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
+
+        // 更新线路2状态为成功
+        this.updateUploadProgress('line2Status', 'success');
+        this.updateUploadProgress('line2', 100);
+
         this.$message.success("线路二上传成功");
+        return { success: true, url: form.line2 };
       } catch (error) {
+        // 更新线路2状态为失败
+        this.updateUploadProgress('line2Status', 'failed');
         this.$message.error("线路二上传失败");
+        return { success: false, error: error.message };
+      }
+    },
+    // 更新上传进度的辅助方法
+    updateUploadProgress(key, value) {
+      this.currentUploadProgress[key] = value;
+
+      // 计算总进度:只有两个线路都成功才算100%
+      if (this.currentUploadProgress.line1Status === 'success' &&
+          this.currentUploadProgress.line2Status === 'success') {
+        this.currentUploadProgress.total = 100;
+      } else if (this.currentUploadProgress.line1Status === 'failed' ||
+                 this.currentUploadProgress.line2Status === 'failed') {
+        // 如果任一线路失败,总进度保持当前状态
+        this.currentUploadProgress.total = Math.min(
+          (this.currentUploadProgress.line1 + this.currentUploadProgress.line2) / 2,
+          99 // 失败时最多99%
+        );
+      } else {
+        // 正常上传中,计算平均进度
+        this.currentUploadProgress.total = Math.min(
+          (this.currentUploadProgress.line1 + this.currentUploadProgress.line2) / 2,
+          99 // 上传中最多99%,只有都成功才100%
+        );
       }
     },
     // 上传视频
     async videoUpload(options) {
-      this.add = true
+      this.isUploading = true;
+      this.form.uploadStatus = 'uploading';
+
       const file = options.file;
-      this.getMediaDuration(file)
+      this.getMediaDuration(file);
+
+      // 重置进度
+      this.currentUploadProgress = {
+        total: 0,
+        line1: 0,
+        line2: 0,
+        line1Status: 'pending',
+        line2Status: 'pending'
+      };
 
-      // 获取第一帧图片
-      await this.getFirstThumbnail(file, this.form);
+      try {
+        // 获取第一帧图片
+        await this.getFirstThumbnail(file, this.form);
+
+        // 并行上传到两个服务器
+        const [line1Result, line2Result] = await Promise.allSettled([
+          this.uploadVideoToTxPcdn(file, this.form, options.onProgress),
+          this.uploadVideoToHwObs(file, this.form, options.onProgress)
+        ]);
+
+        // 检查上传结果
+        const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
+        const line2Success = line2Result.status === 'fulfilled' && line2Result.value.success;
+
+        if (line1Success && line2Success) {
+          // 两个都成功
+          this.form.uploadStatus = 'success';
+          this.form.uploadProgress = {
+            total: 100,
+            line1: 100,
+            line2: 100,
+            line1Status: 'success',
+            line2Status: 'success'
+          };
+          this.currentUploadProgress.total = 100;
+          this.$message.success("视频上传完成!两个线路都上传成功");
+        } else {
+          // 至少有一个失败
+          this.form.uploadStatus = 'failed';
+          this.form.uploadProgress = {
+            total: this.currentUploadProgress.total,
+            line1: this.currentUploadProgress.line1,
+            line2: this.currentUploadProgress.line2,
+            line1Status: this.currentUploadProgress.line1Status,
+            line2Status: this.currentUploadProgress.line2Status
+          };
 
-      // 上传腾讯云pcdn
-      await this.uploadVideoToTxPcdn(file, this.form, options.onProgress);
+          const failedLines = [];
+          if (!line1Success) failedLines.push('线路1');
+          if (!line2Success) failedLines.push('线路2');
 
-      // 上传华为obs
-      await this.uploadVideoToHwObs(file, this.form, options.onProgress);
+          this.$message.error(`视频上传失败!${failedLines.join('、')} 上传失败,请重试`);
+        }
 
-      this.form.fileName = file.name;
-      this.form.fileSize = file.size;
+        this.form.fileName = file.name;
+        this.form.fileSize = file.size;
 
-      this.add = false
+      } catch (error) {
+        this.form.uploadStatus = 'failed';
+        this.$message.error("视频上传过程中发生错误,请重试");
+      } finally {
+        this.isUploading = false;
+      }
     },
     // 获取媒体文件时长
     getMediaDuration(file) {
@@ -1308,7 +1484,7 @@ export default {
       });
     },
 
-    /** 显示上传面板 */
+     /** 显示上传面板 */
     showUploadPanel() {
       this.showUpload = true;
       this.batchUploadForm = {
@@ -1331,7 +1507,6 @@ export default {
     /** 批量上传视频 */
     async batchVideoUpload(options) {
       const file = options.file;
-
       // 创建临时视频对象添加到列表中
       const tempVideo = {
         tempId: Math.random().toString(36).substring(2, 15),
@@ -1348,7 +1523,14 @@ export default {
         typeId: this.batchUploadForm.typeId, // 使用选择的分类
         typeSubId: this.batchUploadForm.typeSubId, // 使用选择的子分类
         projectIds: this.batchUploadForm.projectIds, // 使用选择的项目
-        progress: 0, // 上传进度
+        progress: 0, //  总进度
+        uploadStatus: 'uploading',
+        uploadDetails: {
+          line1: 0,
+          line2: 0,
+          line1Status: 'pending',
+          line2Status: 'pending'
+        },
         file: file // 保存文件引用
       };
 
@@ -1373,15 +1555,53 @@ export default {
         // 获取封面
         await this.getFirstThumbnail(file, tempVideo);
 
-        // 上传到第一个服务器
-        await this.uploadVideoToTxPcdn(file, tempVideo, (event) => {
-            tempVideo.progress = event.percent
-        });
+        // // 上传到第一个服务器
+        // await this.uploadVideoToTxPcdn(file, tempVideo, (event) => {
+        //     tempVideo.progress = event.percent
+        // });
+
+        // // 上传到第二个服务器
+        // await this.uploadVideoToHwObs(file, tempVideo, (event) => {
+        //   tempVideo.progress = event.percent
+        // });
+        // 并行上传到两个服务器
+        const [line1Result, line2Result] = await Promise.allSettled([
+          this.uploadVideoToTxPcdnBatch(file, tempVideo),
+          this.uploadVideoToHwObsBatch(file, tempVideo)
+        ]);
+
+        // 检查上传结果
+        const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
+        const line2Success = line2Result.status === 'fulfilled' && line2Result.value.success;
+        if (line1Success && line2Success) {
+          // 两个都成功,更新进度为100%
+          const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+          if (index !== -1) {
+            this.videoList[index].progress = 100;
+            this.videoList[index].uploadStatus = 'success';
+            this.videoList[index].uploadDetails.line1Status = 'success';
+            this.videoList[index].uploadDetails.line2Status = 'success';
+            this.videoList[index].uploadDetails.line1 = 100;
+            this.videoList[index].uploadDetails.line2 = 100;
+          }
+          this.$message.success(`文件 ${file.name} 上传成功`);
+        } else {
+          // 至少有一个失败
+          const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+          if (index !== -1) {
+            this.videoList[index].uploadStatus = 'failed';
+            this.videoList[index].uploadDetails.line1Status = line1Success ? 'success' : 'failed';
+            this.videoList[index].uploadDetails.line2Status = line2Success ? 'success' : 'failed';
+            // 失败时保持当前进度,不设为100%
+            this.updateBatchProgress(index);
+          }
 
-        // 上传到第二个服务器
-        await this.uploadVideoToHwObs(file, tempVideo, (event) => {
-          tempVideo.progress = event.percent
-        });
+          const failedLines = [];
+          if (!line1Success) failedLines.push('线路1');
+          if (!line2Success) failedLines.push('线路2');
+
+          this.$message.error(`文件 ${file.name} 在 ${failedLines.join('、')} 上传失败`);
+        }
 
       } catch (error) {
         this.$message.error(`文件 ${file.name} 上传失败: ${error.message || '未知错误'}`);
@@ -1626,6 +1846,345 @@ export default {
       this.videoPreviewVisible = false;
       done();
     },
+    // 批量上传 - 腾讯云
+    async uploadVideoToTxPcdnBatch(file, tempVideo) {
+      console.log("--------------",JSON.stringify(tempVideo))
+      try {
+        const data = await uploadObject(file, (progress) => {
+          const progressPercent = Math.floor(progress.percent * 100);
+          const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+          if (index !== -1) {
+            this.videoList[index].uploadDetails.line1 = progressPercent;
+            this.videoList[index].uploadDetails.line1Status = 'uploading';
+            // 更新总进度
+            this.updateBatchProgress(index);
+          }
+        }, 1);
+
+        let line_1 = `${process.env.VUE_APP_VIDEO_LINE_1}${data.urlPath}`;
+        tempVideo.fileKey = data.urlPath.substring(1);
+        tempVideo.videoUrl = line_1;
+        tempVideo.line1 = line_1;
+
+        return { success: true, url: line_1 };
+      } catch (error) {
+        return { success: false, error: error.message };
+      }
+    },
+    // 批量上传 - 华为云
+    async uploadVideoToHwObsBatch(file, tempVideo) {
+      try {
+        const data = await uploadToOBS(file, (progress) => {
+          const progressPercent = Math.floor(progress);
+          const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+          if (index !== -1) {
+            this.videoList[index].uploadDetails.line2 = progressPercent;
+            this.videoList[index].uploadDetails.line2Status = 'uploading';
+            // 更新总进度
+            this.updateBatchProgress(index);
+          }
+        }, 1);
+
+        tempVideo.line2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
+        return { success: true, url: tempVideo.line2 };
+      } catch (error) {
+        return { success: false, error: error.message };
+      }
+    },
+    // 更新批量上传的总进度
+    updateBatchProgress(index) {
+      if (index >= 0 && index < this.videoList.length) {
+        const item = this.videoList[index];
+        const line1Progress = item.uploadDetails.line1 || 0;
+        const line2Progress = item.uploadDetails.line2 || 0;
+
+        // 只有两个线路都成功才算100%
+        if (item.uploadDetails.line1Status === 'success' &&
+            item.uploadDetails.line2Status === 'success') {
+          item.progress = 100;
+        } else if (item.uploadDetails.line1Status === 'failed' ||
+                   item.uploadDetails.line2Status === 'failed') {
+          // 如果任一线路失败,总进度保持当前状态,不超过99%
+          item.progress = Math.min((line1Progress + line2Progress) / 2, 99);
+        } else {
+          // 正常上传中,计算平均进度,不超过99%
+          item.progress = Math.min((line1Progress + line2Progress) / 2, 99);
+        }
+      }
+    },
+    // 重试批量上传
+    async retryBatchUpload(row) {
+      // const index = this.videoList.findIndex(item => item.tempId === row.tempId);
+      // if (index === -1) return;
+
+      // const {line1, line2,line1Status,line2Status} = this.videoList[index].uploadDetails
+
+
+
+      // // 重置状态
+      // this.videoList[index].uploadStatus = 'uploading';
+      // // this.videoList[index].progress = 0;
+      // this.videoList[index].uploadDetails = {
+      //   line1: line1 == 100?line1:0,
+      //   line2: line2 == 100?line2:0,
+      //   line1Status: line1Status =='success'?line1Status:'pending',
+      //   line2Status: line2Status =='success'?line2Status:'pending'
+      // };
+
+      // const tempVideo = this.videoList[index];
+
+      // try {
+      //   // 重新上传
+      //   // const [line1Result, line2Result] = await Promise.allSettled([
+      //   //   this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo),
+      //   //   // this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
+      //   // ]);
+      //   if (line1 !== 100) {
+      //     const line1Result = await this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo)
+      //   }
+
+      //   if (line2 !== 100) {
+      //     const line2Result = await this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
+      //   }
+
+      //   const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
+      //   const line2Success = line2Result.status === 'fulfilled' && line2Result.value.success;
+
+      //   if ( line1 == 100 || line1Success) {
+      //     this.videoList[index].uploadDetails.line1Status = 'success';
+      //     this.videoList[index].uploadDetails.line1 = 100;
+      //     this.$message.success(`文件 ${tempVideo.fileName} 重试上传成功`);
+      //   } else {
+      //     this.videoList[index].uploadDetails.line1Status = line1Success ? 'success' : 'failed';
+      //     this.$message.error(`文件 ${tempVideo.fileName} 重试上传失败`);
+      //   }
+
+      //   if (line2 == 100 || line2Success) {
+      //     this.videoList[index].uploadDetails.line2Status = 'success';
+      //     this.videoList[index].uploadDetails.line2 = 100;
+      //     this.$message.success(`文件 ${tempVideo.fileName} 重试上传成功`);
+      //   } else {
+      //     this.videoList[index].uploadDetails.line2Status = line2Success ? 'success' : 'failed';
+      //     this.$message.error(`文件 ${tempVideo.fileName} 重试上传失败`);
+      //   }
+
+      //   // if (line1Success && line2Success) {
+      //   //   this.videoList[index].progress = 100;
+      //   //   this.videoList[index].uploadStatus = 'success';
+      //   //   this.videoList[index].uploadDetails.line1Status = 'success';
+      //   //   this.videoList[index].uploadDetails.line2Status = 'success';
+      //   //   this.videoList[index].uploadDetails.line1 = 100;
+      //   //   this.videoList[index].uploadDetails.line2 = 100;
+      //   //   this.$message.success(`文件 ${tempVideo.fileName} 重试上传成功`);
+      //   // } else {
+      //   //   this.videoList[index].uploadStatus = 'failed';
+      //   //   this.videoList[index].uploadDetails.line1Status = line1Success ? 'success' : 'failed';
+      //   //   this.videoList[index].uploadDetails.line2Status = line2Success ? 'success' : 'failed';
+      //   //   this.$message.error(`文件 ${tempVideo.fileName} 重试上传失败`);
+      //   // }
+      // } catch (error) {
+      //   this.videoList[index].uploadStatus = 'failed';
+      //   this.$message.error(`文件 ${tempVideo.fileName} 重试上传失败`);
+      // }
+      const index = this.videoList.findIndex(item => item.tempId === row.tempId);
+      if (index === -1) return;
+
+      const tempVideo = this.videoList[index];
+      const uploadDetails = tempVideo.uploadDetails || {};
+
+      // 检查哪些线路需要重试
+      const needRetryLine1 = uploadDetails.line1Status === 'failed' || uploadDetails.line1Status === 'pending';
+      const needRetryLine2 = uploadDetails.line2Status === 'failed' || uploadDetails.line2Status === 'pending';
+
+      if (!needRetryLine1 && !needRetryLine2) {
+        this.$message.info('所有线路都已上传成功,无需重试');
+        return;
+      }
+
+      // 更新整体状态为上传中
+      this.videoList[index].uploadStatus = 'uploading';
+
+      // 只重置需要重试的线路状态
+      if (needRetryLine1) {
+        this.videoList[index].uploadDetails.line1 = 0;
+        this.videoList[index].uploadDetails.line1Status = 'pending';
+      }
+      if (needRetryLine2) {
+        this.videoList[index].uploadDetails.line2 = 0;
+        this.videoList[index].uploadDetails.line2Status = 'pending';
+      }
+
+      try {
+        const uploadPromises = [];
+
+        // 根据需要重试的线路创建上传任务
+        if (needRetryLine1) {
+          uploadPromises.push(
+            this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo)
+              .then(result => ({ line: 'line1', result }))
+              .catch(error => ({ line: 'line1', result: { success: false, error: error.message } }))
+          );
+        } else {
+          // 如果线路1不需要重试,创建一个已成功的Promise
+          uploadPromises.push(Promise.resolve({ line: 'line1', result: { success: true } }));
+        }
+
+        if (needRetryLine2) {
+          uploadPromises.push(
+            this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
+              .then(result => ({ line: 'line2', result }))
+              .catch(error => ({ line: 'line2', result: { success: false, error: error.message } }))
+          );
+        } else {
+          // 如果线路2不需要重试,创建一个已成功的Promise
+          uploadPromises.push(Promise.resolve({ line: 'line2', result: { success: true } }));
+        }
+
+        // 等待所有上传任务完成
+        const results = await Promise.all(uploadPromises);
+
+        // 处理结果
+        let line1Success = true;
+        let line2Success = true;
+        let retryMessages = [];
+
+        results.forEach(({ line, result }) => {
+          if (line === 'line1') {
+            line1Success = result.success;
+            if (needRetryLine1) {
+              if (result.success) {
+                this.videoList[index].uploadDetails.line1Status = 'success';
+                this.videoList[index].uploadDetails.line1 = 100;
+                retryMessages.push('线路1重试成功');
+              } else {
+                this.videoList[index].uploadDetails.line1Status = 'failed';
+                retryMessages.push('线路1重试失败');
+              }
+            }
+          } else if (line === 'line2') {
+            line2Success = result.success;
+            if (needRetryLine2) {
+              if (result.success) {
+                this.videoList[index].uploadDetails.line2Status = 'success';
+                this.videoList[index].uploadDetails.line2 = 100;
+                retryMessages.push('线路2重试成功');
+              } else {
+                this.videoList[index].uploadDetails.line2Status = 'failed';
+                retryMessages.push('线路2重试失败');
+              }
+            }
+          }
+        });
+
+        // 更新总体状态和进度
+        if (line1Success && line2Success) {
+          this.videoList[index].progress = 100;
+          this.videoList[index].uploadStatus = 'success';
+          this.$message.success(`文件 ${tempVideo.fileName} 重试完成:${retryMessages.join(',')}`);
+        } else {
+          this.videoList[index].uploadStatus = 'failed';
+          // 重新计算进度
+          this.updateBatchProgress(index);
+          this.$message.error(`文件 ${tempVideo.fileName} 重试完成:${retryMessages.join(',')}`);
+        }
+
+      } catch (error) {
+        this.videoList[index].uploadStatus = 'failed';
+        this.$message.error(`文件 ${tempVideo.fileName} 重试过程中发生错误:${error.message || '未知错误'}`);
+      }
+    },
+    // 重试单个上传
+    async retryUpload(row) {
+      this.$confirm('确认要重新上传该视频吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async () => {
+        // 这里需要重新触发上传,但由于原始文件可能已经不存在
+        // 建议用户重新选择文件上传
+        this.$message.info('请重新选择文件进行上传');
+        this.handleUpdate(row);
+      }).catch(() => {});
+    },
+    // 获取上传状态图标
+    getUploadStatusIcon(status) {
+      switch (status) {
+        case 'success':
+          return 'el-icon-success';
+        case 'failed':
+          return 'el-icon-error';
+        case 'uploading':
+          return 'el-icon-loading';
+        default:
+          return 'el-icon-time';
+      }
+    },
+    // 获取上传状态颜色
+    getUploadStatusColor(status) {
+      switch (status) {
+        case 'success':
+          return '#67C23A';
+        case 'failed':
+          return '#F56C6C';
+        case 'uploading':
+          return '#409EFF';
+        default:
+          return '#909399';
+      }
+    },
+
+    // 获取上传状态文本
+    getUploadStatusText(status) {
+      switch (status) {
+        case 'success':
+          return '上传成功';
+        case 'failed':
+          return '上传失败';
+        case 'uploading':
+          return '上传中';
+        default:
+          return '待上传';
+      }
+    },
+
+    // 获取线路状态图标
+    getLineStatusIcon(status) {
+      switch (status) {
+        case 'success':
+          return 'el-icon-check';
+        case 'failed':
+          return 'el-icon-close';
+        case 'uploading':
+          return 'el-icon-loading';
+        default:
+          return 'el-icon-minus';
+      }
+    },
+
+    // 获取线路状态颜色
+    getLineStatusColor(status) {
+      switch (status) {
+        case 'success':
+          return '#67C23A';
+        case 'failed':
+          return '#F56C6C';
+        case 'uploading':
+          return '#409EFF';
+        default:
+          return '#C0C4CC';
+      }
+    },
+
+    // 获取进度条状态
+    getProgressStatus(row) {
+      if (row.progress === 100 && row.uploadStatus === 'success') {
+        return 'success';
+      } else if (row.uploadStatus === 'failed') {
+        return 'exception';
+      }
+      return '';
+    },
+
   }
 }
 </script>
@@ -1641,6 +2200,124 @@ export default {
   margin-left: 5px;
 }
 
+/* 上传状态样式 */
+.upload-status-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+}
+
+.status-indicator {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.status-text {
+  font-size: 12px;
+}
+
+.upload-details {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  font-size: 11px;
+}
+
+.line-status {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.line-label {
+  min-width: 35px;
+  color: #606266;
+}
+
+.line-progress {
+  color: #909399;
+}
+
+/* 双线上传进度样式 */
+.dual-upload-progress {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+}
+
+.total-progress {
+  margin-bottom: 4px;
+}
+
+.line-progress-container {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  width: 100%;
+}
+
+.line-progress-item {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 10px;
+}
+
+.line-label {
+  min-width: 30px;
+  color: #606266;
+}
+
+.line-status-text {
+  min-width: 25px;
+  color: #909399;
+  font-size: 10px;
+}
+
+/* 批量上传进度样式 */
+.batch-upload-progress {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.total-progress-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  white-space: nowrap; /* 防止换行 */
+  min-width: 0; /* 允许flex项目收缩 */
+}
+
+.progress-label {
+  font-size: 12px;
+  color: #606266;
+  min-width: 40px;
+
+}
+
+.line-progress-rows {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+}
+
+.line-progress-row {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 11px;
+}
+
+.line-percentage {
+  min-width: 30px;
+  color: #909399;
+  font-size: 11px;
+}
+
 ::v-deep .upload-icon {
   font-size: 28px;
   color: #8c939d;

+ 5 - 0
src/views/his/integralGoods/index.vue

@@ -122,6 +122,7 @@
         </template>
       </el-table-column>
       <el-table-column label="所需积分" align="center" prop="integral" />
+      <el-table-column label="需支付金额" align="center" prop="cash" />
       <el-table-column label="排序" align="center" prop="sort" />
       <el-table-column label="库存" align="center" prop="stock" />
       <el-table-column label="创建时间" align="center" prop="createTime" width="180"/>
@@ -188,6 +189,9 @@
         <el-form-item label="所需积分" prop="integral">
           <el-input-number v-model="form.integral"  :min="0"  label="所需积分"></el-input-number>
         </el-form-item>
+        <el-form-item label="支付金额" prop="cash">
+          <el-input-number v-model="form.cash"  :min="0" :precision="2" :step="0.1"  label="需支付金额"></el-input-number>
+        </el-form-item>
         <el-form-item label="商品编号" prop="barCode">
           <el-input v-model="form.barCode" placeholder="请输入商品编号"  style="width: 200px;"/>
         </el-form-item>
@@ -412,6 +416,7 @@ export default {
         goodsType: null,
         status: 0,
         integral: null,
+        cash: null,
         sort: null,
         stock: null,
         descs: null,

+ 1 - 0
src/views/his/integralOrder/index.vue

@@ -124,6 +124,7 @@
       <el-table-column label="用户电话" align="center" prop="userPhone" />
       <el-table-column label="用户地址" align="center" prop="userAddress" show-overflow-tooltip />
       <el-table-column label="支付积分" align="center" prop="integral" />
+      <el-table-column label="支付金额" align="center" prop="cash" />
       <el-table-column label="状态" align="center" prop="status">
         <template slot-scope="scope">
           <dict-tag :options="statusOptions" :value="scope.row.status"/>

+ 78 - 141
src/views/his/user/index.vue

@@ -112,16 +112,6 @@
     </el-form>
 
     <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button
-          type="primary"
-          icon="el-icon-user"
-          size="mini"
-          @click="handleChangeCompanyUser"
-          :disabled="multiple"
-          v-hasPermi="['company:companyUser:change']"
-        >更换会员归属</el-button>
-      </el-col>
       <el-col :span="1.5">
         <el-button
           type="warning"
@@ -138,17 +128,19 @@
 
     <el-table height="660" v-loading="loading" border :data="userList" @selection-change="handleSelectionChange" >
       <el-table-column type="selection" width="55" align="center" />
-
       <el-table-column label="会员id" align="center" prop="userId" width="150px"/>
       <el-table-column label="用户昵称" align="center" prop="nickName" width="150px"/>
-      <el-table-column label="用户头像" align="center" prop="avatar" >
-         <template slot-scope="scope">
-                 <el-image v-if="scope.row.avatar!=null"
-                     style="width: 50px;"
-                     :src="scope.row.avatar"
-                    >
-                 </el-image>
-         </template>
+      <el-table-column label="会员头像" align="center" width="80">
+        <template slot-scope="scope">
+          <el-popover
+            placement="right"
+            title=""
+            trigger="hover"
+          >
+            <img slot="reference" :src="scope.row.avatar" width="50" >
+            <img :src="scope.row.avatar" style="max-width: 120px;">
+          </el-popover>
+        </template>
       </el-table-column>
       <el-table-column label="手机号码" align="center" prop="phone" width="150px" />
       <el-table-column label="用户积分" align="center" prop="integral" />
@@ -188,7 +180,12 @@
             @click="handleDelete(scope.row)"
             v-hasPermi="['his:user:remove']"
           >删除</el-button>
-
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleAddPoint(scope.row)"
+            v-hasPermi="['his:user:addPoint']"
+          >添加积分</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -235,40 +232,26 @@
          <userDetailsByNew  ref="userDetailsByNew" />
        </el-drawer>
 
-    <!-- 更换会员归属对话框 -->
-    <el-dialog title="更换会员归属" :visible.sync="changeCompanyUserOpen" width="500px" append-to-body>
-      <el-form ref="changeCompanyUserForm" :model="changeCompanyUserForm" :rules="changeCompanyUserRules" label-width="100px">
-        <el-form-item label="选择公司" prop="companyId">
-          <el-select v-model="changeCompanyUserForm.companyId" placeholder="请选择公司" style="width: 100%" @change="handleCompanyChange">
-            <el-option
-              v-for="item in companyOptions"
-              :key="item.companyId"
-              :label="item.companyName"
-              :value="item.companyId"
-            />
-          </el-select>
+    <el-dialog :title="addPointDialog.title" :visible.sync="addPointDialog.open" width="500px" append-to-body>
+      <el-form ref="addPointForm" :model="addPointDialog.form" :rules="addPointDialog.rules" label-width="100px">
+        <el-form-item label="积分" prop="point">
+          <el-input-number :min="0" :step="1" :precision="0" v-model="addPointDialog.form.point" placeholder="请输入积分" />
+          <div style="color: #a95812">积分不能为小数</div>
         </el-form-item>
-        <el-form-item label="选择销售" prop="companyUserId">
-          <el-select v-model="changeCompanyUserForm.companyUserId" placeholder="请选择销售" style="width: 100%" @change="handleCompanyUserChange">
-            <el-option
-              v-for="item in companyUserOptions"
-              :key="item.userId"
-              :label="item.nickName + '_' + item.userName"
-              :value="item.userId"
-            />
-          </el-select>
+        <el-form-item label="备注" prop="remark">
+          <el-input type="textarea" v-model="addPointDialog.form.remark" placeholder="请输入备注说明" />
         </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="submitChangeCompanyUserForm">确 定</el-button>
-        <el-button @click="cancelChangeCompanyUser">取 消</el-button>
+        <el-button type="primary" @click="submitAddPointForm">确 定</el-button>
+        <el-button @click="cancelAddPoint">取 消</el-button>
       </div>
     </el-dialog>
   </div>
 </template>
 
 <script>
-import { listUser, getUser, delUser, addUser, updateUser, exportUser } from "@/api/his/user";
+import { listUser, getUser, delUser, addUser, updateUser, exportUser, addPoint } from "@/api/his/user";
 import { getCompanyUserList, changeCompanyUser, getCompanyList } from '@/api/company/companyUser';
 import userDetails from '../../components/his/userDetails.vue';
 import userDetailsByNew from './userDetails.vue';
@@ -284,25 +267,8 @@ export default {
       queryCompanyUserId:null,
       companyName: null,
       companyUserNickName: null,
-      // 更换会员归属表单校验
-      changeCompanyUserRules: {
-        companyId: [
-          { required: true, message: '请选择公司', trigger: 'change' }
-        ],
-        companyUserId: [
-          { required: true, message: '请选择销售', trigger: 'change' }
-        ]
-      },
       companyOptions: [],
       companyUserOptions: [],
-      // 更换会员归属对话框
-      changeCompanyUserOpen: false,
-      // 更换会员归属表单
-      changeCompanyUserForm: {
-        companyId: null,
-        companyUserId: null,
-        userIds: []
-      },
       show:{
               title:"用户详情",
               open:false,
@@ -369,6 +335,28 @@ export default {
         integral: [
           { required: true, message: "用户积分不能为空", trigger: "blur" }
         ],
+      },
+      addPointDialog: {
+        open: false,
+        title: "添加积分",
+        form: {
+          userId: null,
+          point: null,
+          remark: null
+        },
+        rules: {
+          point: [
+            { required: true, message: "请输入积分", trigger: "blur" },
+            { validator: (rule, value, callback) => {
+                if (Number(value) > 0) {
+                  callback(); // 验证通过
+                } else {
+                  callback(new Error("积分必须大于0"));
+                }
+              },
+              trigger: "blur" }
+          ]
+        }
       }
     };
   },
@@ -429,26 +417,6 @@ export default {
         this.companyQueryUserOptions = [];
       }
      },
-    handleCompanyChange(companyId) {
-      // 清空已选择的销售
-      this.changeCompanyUserForm.companyUserId = null;
-      // 根据公司ID获取对应的销售列表
-      if (companyId) {
-        getCompanyUserList({ companyId: companyId }).then(response => {
-          if (response.code === 200) {
-            this.companyUserOptions = response.data;
-          } else {
-            this.$message.error(response.msg || '获取销售列表失败');
-            this.companyUserOptions = [];
-          }
-        }).catch(() => {
-          this.$message.error('获取销售列表失败');
-          this.companyUserOptions = [];
-        });
-      } else {
-        this.companyUserOptions = [];
-      }
-    },
     /** 查询用户列表 */
     getList() {
       this.loading = true;
@@ -564,6 +532,35 @@ export default {
           this.msgSuccess("删除成功");
         }).catch(() => {});
     },
+    handleAddPoint(row) {
+      console.log(row)
+      this.resetForm("addPointForm");
+      this.addPointDialog.form = {
+        userId: row.userId,
+        point: 0,
+        remark: ''
+      }
+      this.addPointDialog.open = true
+    },
+    submitAddPointForm() {
+      this.$refs["addPointForm"].validate(valid => {
+        if (valid) {
+          addPoint(this.addPointDialog.form).then(response => {
+            const {code} = response
+            if (code !== 200) {
+              this.msgError(response.msg || '添加失败');
+              return
+            }
+            this.msgSuccess("添加成功");
+            this.addPointDialog.open = false;
+            this.getList();
+          });
+        }
+      });
+    },
+    cancelAddPoint() {
+      this.addPointDialog.open = false;
+    },
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = this.queryParams;
@@ -579,66 +576,6 @@ export default {
           this.exportLoading = false;
         }).catch(() => {});
     },
-    /** 更换会员归属按钮操作 */
-    handleChangeCompanyUser() {
-      // 获取公司下拉列表
-      getCompanyList().then(response => {
-        if (response.code === 200) {
-          this.companyOptions = response.data;
-          // 重置表单和销售列表
-          this.resetCompanyUserForm();
-          this.companyUserOptions = [];
-          this.changeCompanyUserOpen = true;
-        } else {
-          this.$message.error(response.msg || '获取公司列表失败');
-        }
-      }).catch(() => {
-        this.$message.error('获取公司列表失败');
-      });
-    },
-
-    /** 取消更换会员归属 */
-    cancelChangeCompanyUser() {
-      this.changeCompanyUserOpen = false;
-      this.resetCompanyUserForm();
-    },
-    /** 重置更换会员归属表单 */
-    resetCompanyUserForm() {
-      this.changeCompanyUserForm = {
-        companyId: null,
-        companyUserId: null,
-        userIds: []
-      };
-      this.resetForm("changeCompanyUserForm");
-    },
-    /** 提交更换会员归属 */
-    submitChangeCompanyUserForm() {
-      this.$refs["changeCompanyUserForm"].validate(valid => {
-        if (valid) {
-          // 调用更换会员归属接口
-          // 检查companyId是否已设置
-          if (!this.changeCompanyUserForm.companyId) {
-            this.$message.error('请选择公司');
-            return;
-          }
-
-          changeCompanyUser(this.ids, {
-            companyUserId: this.changeCompanyUserForm.companyUserId,
-            companyId: this.changeCompanyUserForm.companyId
-          }).then(response => {
-            if (response.code === 200) {
-              this.msgSuccess("操作成功");
-              this.changeCompanyUserOpen = false;
-              this.getList();
-            } else {
-              this.$message.error(response.msg || '操作失败');
-            }
-          }).catch(() => {
-            this.$message.error('操作失败');
-          });
-        }
-      });
-    }
   }
 };
 </script>

+ 794 - 0
src/views/his/user/indexProject.vue

@@ -0,0 +1,794 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="会员ID" prop="userId">
+        <el-input
+
+          v-model="queryParams.userId"
+          placeholder="请输入会员ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="会员昵称" prop="nickname">
+        <el-input
+
+          v-model="queryParams.nickname"
+          placeholder="请输入会员昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="手机号码" prop="phone">
+        <el-input
+          v-model="queryParams.phone"
+          placeholder="请输入手机号码"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="注册时间" prop="createTimeRange">
+        <el-date-picker clearable size="small" style="width: 340px"
+                        v-model="dateRange"
+                        type="daterange"
+                        value-format="yyyy-MM-dd"
+                        range-separator="至"
+                        start-placeholder="开始日期"
+                        end-placeholder="结束日期"
+                        @change="handleDateRangeChange">
+        </el-date-picker>
+      </el-form-item>
+
+
+
+      <el-form-item label="所属公司" prop="companyName">
+        <el-select
+          v-model="queryParams.companyId"
+          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="companyUserNickName">
+
+        <el-select
+          v-model="queryParams.companyUserNickName"
+          placeholder="请选择所属销售"
+          clearable
+          filterable
+          size="small"
+        >
+          <el-option
+            v-for="item in companyQueryUserOptions"
+            :key="item.userId"
+            :label="item.nickName"
+            :value="item.nickName">
+          </el-option>
+        </el-select>
+      </el-form-item>
+
+
+
+
+      <!--      <el-form-item label="推线编码" prop="registerCode">-->
+      <!--        <el-input-->
+
+      <!--          v-model="queryParams.registerCode"-->
+      <!--          placeholder="请输入推线编码"-->
+      <!--          clearable-->
+      <!--          size="small"-->
+      <!--          @keyup.enter.native="handleQuery"-->
+      <!--        />-->
+      <!--      </el-form-item>-->
+      <el-form-item label="状态" prop="status">
+        <el-select  v-model="queryParams.status" placeholder="请选择状态" clearable size="small" >
+          <el-option
+            v-for="item in statusOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <!--      <el-form-item label="会员等级" prop="status">-->
+      <!--        <el-select   v-model="queryParams.level" placeholder="请选择会员等级" clearable size="small" >-->
+      <!--         <el-option-->
+      <!--                v-for="item in userLevelOptions"-->
+      <!--                :key="item.dictValue"-->
+      <!--                :label="item.dictLabel"-->
+      <!--                :value="item.dictValue"-->
+      <!--              />-->
+      <!--        </el-select>-->
+      <!--      </el-form-item>-->
+      <!--      <el-form-item label="推广员" prop="isPromoter">-->
+      <!--        <el-select  v-model="queryParams.isPromoter" placeholder="请选择" clearable size="small" >-->
+      <!--         <el-option-->
+      <!--                v-for="item in userIsPromoterOptions"-->
+      <!--                :key="item.dictValue"-->
+      <!--                :label="item.dictLabel"-->
+      <!--                :value="item.dictValue"-->
+      <!--              />-->
+      <!--        </el-select>-->
+      <!--      </el-form-item>-->
+      <el-form-item label="项目" prop="projectId">
+        <el-select  v-model="queryParams.projectId" placeholder="请选择项目" clearable size="small" >
+          <el-option
+            v-for="item in projectOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" 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="primary"
+          icon="el-icon-user"
+          size="mini"
+          @click="handleChangeCompanyUser"
+          :disabled="multiple"
+          v-hasPermi="['company:companyUser:change']"
+        >更换会员归属</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          icon="el-icon-download"
+          size="mini"
+          @click="handleExport"
+          v-hasPermi="['store:user:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table  height="500" border v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="userId" />
+      <el-table-column label="项目" align="center" prop="projectId">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.projectId !== null">{{ getProjectLabel(scope.row.projectId,scope.row) }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="会员昵称" align="center" prop="nickname" />
+      <el-table-column label="会员头像" align="center" width="80">
+        <template slot-scope="scope">
+          <el-popover
+            placement="right"
+            title=""
+            trigger="hover"
+          >
+            <img slot="reference" :src="scope.row.avatar" width="50" >
+            <img :src="scope.row.avatar" style="max-width: 120px;">
+          </el-popover>
+        </template>
+      </el-table-column>
+      <el-table-column label="手机号码" align="center" prop="phone" />
+      <el-table-column label="用户余额" align="center" prop="nowMoney" />
+      <!--      <el-table-column label="推广佣金" align="center" prop="brokeragePrice" />-->
+      <el-table-column label="积分" align="center" prop="integral" />
+      <el-table-column label="会员注册时间" align="center" prop="createTime" />
+      <!--      <el-table-column label="累计消费金额" align="center" prop="totalAmount" />-->
+      <!--      <el-table-column label="上次消费时间" align="center" prop="lastBuyTime" />-->
+      <!--      <el-table-column label="上次消费金额(元)" align="center" prop="number" />-->
+      <!--      <el-table-column label="会员等级" align="center" prop="level" >-->
+      <!--          <template slot-scope="scope">-->
+      <!--              <el-tag prop="status" v-for="(item, index) in userLevelOptions"    v-if="scope.row.level==item.dictValue">{{item.dictLabel}}</el-tag>-->
+      <!--          </template>-->
+      <!--      </el-table-column>-->
+      <!--      <el-table-column label="是否允许下单" align="center" prop="isShow" >-->
+      <!--        <template slot-scope="scope">-->
+      <!--          <el-tag prop="isShow" v-for="(item, index) in isShowOptions"    v-if="scope.row.isShow==item.dictValue">{{item.dictLabel}}</el-tag>-->
+      <!--        </template>-->
+      <!--      </el-table-column>-->
+      <!--      <el-table-column label="推广员" align="center" prop="isPromoter" >-->
+      <!--          <template slot-scope="scope">-->
+      <!--              <el-tag prop="status" v-for="(item, index) in userIsPromoterOptions"    v-if="scope.row.isPromoter==item.dictValue">{{item.dictLabel}}</el-tag>-->
+      <!--          </template>-->
+      <!--      </el-table-column>-->
+      <el-table-column label="状态" align="center" prop="status" >
+        <template slot-scope="scope">
+          <el-tag prop="status" v-for="(item, index) in statusOptions"    v-if="scope.row.status==item.dictValue">{{item.dictLabel}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="所属公司" align="center" prop="companyName" />
+      <el-table-column label="所属销售" align="center" prop="companyUserNickName" />
+      <!--      <el-table-column label="创建时间" align="center" prop="createTime" />-->
+      <!--      <el-table-column label="累计佣金" align="center" prop="registerDate" />-->
+      <!--      <el-table-column label="可提现佣金" align="center" prop="registerCode" />-->
+      <!--      <el-table-column label="冻结佣金" align="center" prop="source" />-->
+      <!--      <el-table-column label="已提现佣金" align="center" prop="remark" />-->
+      <el-table-column label="看课数量" align="center" prop="watchCourseCount" />
+      <el-table-column label="参与营期数" align="center" prop="partCourseCount" />
+      <el-table-column label="最后看课时间" align="center" prop="lastWatchDate" width="160">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.lastWatchDate) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center"  width="150px"   class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['store:user:edit']"
+          >修改</el-button>
+
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleShow(scope.row)"
+            v-hasPermi="['store:user:query']"
+          >查看</el-button>
+
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['store:user:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改用户对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+        <el-form-item label="会员头像" prop="avatar">
+          <el-popover
+            placement="right"
+            title=""
+            trigger="hover"
+          >
+            <img slot="reference" :src="form.avatar" width="80">
+            <img :src="form.avatar" style="max-width: 80px;">
+          </el-popover>
+        </el-form-item>
+        <el-form-item label="会员昵称" prop="nickname">
+          <el-input v-model="form.nickname" disabled placeholder="请输入用户昵称" />
+        </el-form-item>
+        <el-form-item label="手机号码" prop="phone">
+          <el-input v-model="form.phone" disabled placeholder="请输入手机号码" />
+        </el-form-item>
+        <el-form-item label="最后一次登录ip" prop="lastIp">
+          <el-input v-model="form.lastIp" disabled placeholder="请输入最后一次登录ip" />
+        </el-form-item>
+        <!-- <el-form-item label="用户余额" prop="nowMoney">
+          <el-input v-model="form.nowMoney" disabled placeholder="请输入用户余额" />
+        </el-form-item> -->
+        <!-- <el-form-item label="积分" prop="integral">
+          <el-input v-model="form.integral" disabled placeholder="请输入用户积分" />
+        </el-form-item> -->
+        <el-form-item label="进线日期" prop="registerDate">
+          <el-date-picker clearable size="small"
+                          v-model="form.registerDate"
+                          type="date"
+                          value-format="yyyy-MM-dd"
+                          placeholder="选择进线日期">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="推线编码" prop="registerCode">
+          <el-input v-model="form.registerCode" placeholder="请输入推线编码" />
+        </el-form-item>
+        <el-form-item label="渠道来源" prop="source">
+          <el-input v-model="form.source" placeholder="请输入渠道来源" />
+        </el-form-item>
+        <el-form-item label="会员等级" prop="level">
+          <el-select style="width: 200px" v-model="form.level" placeholder="请选择会员等级" clearable size="small" >
+            <el-option
+              v-for="item in userLevelOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="是否为推广员" prop="isPromoter">
+          <el-select style="width: 200px" v-model="form.isPromoter" placeholder="请选择" clearable size="small" >
+            <el-option
+              v-for="item in userIsPromoterOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态" prop="level">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="item.dictValue" v-for="item in statusOptions" >{{item.dictLabel}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="是否展示" prop="isShow">
+          <el-radio-group v-model="form.isShow">
+            <el-radio :label="item.dictValue" v-for="item in isShowOptions" >{{item.dictLabel}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="用户备注" prop="remark">
+          <el-input v-model="form.remark" 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-drawer size="75%" :title="show.title" :visible.sync="show.open">
+      <userDetailsByNew  ref="userDetailsByNew" />
+    </el-drawer>
+
+    <!-- 更换会员归属对话框 -->
+    <el-dialog title="更换会员归属" :visible.sync="changeCompanyUserOpen" width="500px" append-to-body>
+      <el-form ref="changeCompanyUserForm" :model="changeCompanyUserForm" :rules="changeCompanyUserRules" label-width="100px">
+        <el-form-item label="选择公司" prop="companyId">
+          <el-select v-model="changeCompanyUserForm.companyId" placeholder="请选择公司" style="width: 100%" @change="handleCompanyChange">
+            <el-option
+              v-for="item in companyOptions"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="选择销售" prop="companyUserId">
+          <el-select v-model="changeCompanyUserForm.companyUserId" placeholder="请选择销售" style="width: 100%" @change="handleCompanyUserChange">
+            <el-option
+              v-for="item in companyUserOptions"
+              :key="item.userId"
+              :label="item.nickName + '_' + item.userName"
+              :value="item.userId"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitChangeCompanyUserForm">确 定</el-button>
+        <el-button @click="cancelChangeCompanyUser">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {listUserByProject, getUser, addUser, updateUser, exportUser, delUserCompanyUser} from "@/api/his/user";
+import { getCompanyUserList, changeCompanyUser, getCompanyList } from '@/api/company/companyUser';
+import userDetailsByNew from '@/views/his/user/userDetails.vue'
+export default {
+  name: "User",
+  components: { userDetailsByNew },
+  data() {
+    return {
+      companyQueryOptions:[],
+      companyQueryUserOptions:[],
+      userIsPromoterOptions:[],
+      userLevelOptions:[],
+      statusOptions:[],
+      isShowOptions:[],
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      show:{
+        title:"会员详情",
+        open:false,
+      },
+      // 用户表格数据
+      userList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 日期范围
+      dateRange: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        username: null,
+        password: null,
+        realName: null,
+        birthday: null,
+        idCard: null,
+        mark: null,
+        nickname: null,
+        avatar: null,
+        phone: null,
+        lastIp: null,
+        nowMoney: null,
+        brokeragePrice: null,
+        integral: null,
+        signNum: null,
+        status: null,
+        level: null,
+        spreadUserId: null,
+        spreadTime: null,
+        userType: null,
+        isPromoter: null,
+        payCount: null,
+        spreadCount: null,
+        addres: null,
+        wxProfile: null,
+        isDel: null,
+        startCreateTime: null,
+        endCreateTime: null,
+        companyId: null,
+        companyUserNickName: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        status: [
+          { required: true, message: "状态不能为空", trigger: "blur" }
+        ],
+        level: [
+          { required: true, message: "等级不能为空", trigger: "blur" }
+        ],
+
+      },
+      // 更换会员归属对话框
+      changeCompanyUserOpen: false,
+      // 更换会员归属表单
+      changeCompanyUserForm: {
+        companyId: null,
+        companyUserId: null,
+        userIds: []
+      },
+      // 更换会员归属表单校验
+      changeCompanyUserRules: {
+        companyId: [
+          { required: true, message: '请选择公司', trigger: 'change' }
+        ],
+        companyUserId: [
+          { required: true, message: '请选择销售', trigger: 'change' }
+        ]
+      },
+      // 销售选项
+      companyUserOptions: [],
+      companyOptions: [],
+      projectOptions: [],
+      selectedUser: []
+    };
+  },
+  created() {
+    this.getDicts("user_status").then((response) => {
+      this.statusOptions = response.data;
+    });
+    this.getDicts("user_level").then((response) => {
+      this.userLevelOptions = response.data;
+    });
+    this.getDicts("sys_company_or").then((response) => {
+      this.isShowOptions = response.data;
+    });
+
+    this.getDicts("user_is_promoter").then((response) => {
+      this.userIsPromoterOptions = response.data;
+    });
+    this.getDicts("sys_course_project").then(response => {
+      this.projectOptions = response.data;
+    });
+
+    this.getList();
+
+    getCompanyList().then(response => {
+      if (response.code === 200) {
+        this.companyQueryOptions = response.data;
+      }});
+  },
+  methods: {
+
+    handleQueryCompanyChange(companyId){
+      // 清空已选择的销售
+      this.queryCompanyUserId = null;
+      // 根据公司ID获取对应的销售列表
+      if (companyId) {
+        getCompanyUserList({ companyId: companyId }).then(response => {
+          if (response.code === 200) {
+            this.companyQueryUserOptions = response.data;
+          } else {
+            this.$message.error(response.msg || '获取销售列表失败');
+            this.companyQueryUserOptions = [];
+          }
+        }).catch(() => {
+          this.$message.error('获取销售列表失败');
+          this.companyQueryUserOptions = [];
+        });
+      } else {
+        this.companyQueryUserOptions = [];
+      }
+    },
+
+
+    /** 查询用户列表 */
+    getList() {
+      this.loading = true;
+      listUserByProject(this.queryParams).then(response => {
+        this.userList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        userId: null,
+        username: null,
+        password: null,
+        realName: null,
+        birthday: null,
+        idCard: null,
+        mark: null,
+        nickname: null,
+        avatar: null,
+        phone: null,
+        createTime: null,
+        updateTime: null,
+        lastIp: null,
+        nowMoney: null,
+        brokeragePrice: null,
+        integral: null,
+        signNum: null,
+        status: 0,
+        level: 0,
+        spreadUserId: null,
+        spreadTime: null,
+        userType: null,
+        isPromoter: null,
+        payCount: null,
+        spreadCount: null,
+        addres: null,
+        wxProfile: null,
+        isDel: null,
+        isShow: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRange = [];
+      this.resetForm("queryForm");
+      this.queryParams.companyId = null;
+      this.queryParams.companyUserNickName = null;
+      this.companyQueryUserOptions = null;
+      this.handleQuery();
+    },
+    /** 处理日期范围变化 */
+    handleDateRangeChange(dates) {
+      if (dates) {
+        this.queryParams.startCreateTime = dates[0];
+        this.queryParams.endCreateTime = dates[1];
+      } else {
+        this.queryParams.startCreateTime = null;
+        this.queryParams.endCreateTime = null;
+      }
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.userId)
+      this.selectedUser = selection.map(item => {return {userId: item.userId, projectId: item.projectId}})
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加用户";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const userId = row.userId || this.ids
+      getUser(userId).then(response => {
+        this.form = response.data;
+        this.form.status = response.data.status.toString();
+        this.form.isShow = response.data.isShow.toString();
+        this.form.level = response.data.level.toString();
+        this.form.isPromoter = response.data.isPromoter.toString();
+        this.open = true;
+        this.title = "修改用户";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.userId != null) {
+            updateUser(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          } else {
+            addUser(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const companyUserId = row.companyUserId;
+      this.$confirm('是否确认删除用户编号为"' + companyUserId + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delUserCompanyUser(companyUserId);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出当前用户数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return exportUser(queryParams);
+      }).then(response => {
+        console.log(response)
+        this.download(response.msg);
+      }).catch(function() {});
+    }
+    ,handleShow(row){
+      var that=this;
+      that.show.open=true;
+      setTimeout(() => {
+        that.$refs.storeUserDetails.getDetails(row.userId);
+      }, 200);
+    },
+    /** 更换会员归属按钮操作 */
+    handleChangeCompanyUser() {
+      // 获取公司下拉列表
+      getCompanyList().then(response => {
+        if (response.code === 200) {
+          this.companyOptions = response.data;
+          // 重置表单和销售列表
+          this.resetCompanyUserForm();
+          this.companyUserOptions = [];
+          this.changeCompanyUserOpen = true;
+        } else {
+          this.$message.error(response.msg || '获取公司列表失败');
+        }
+      }).catch(() => {
+        this.$message.error('获取公司列表失败');
+      });
+    },
+    /** 销售选择变化 */
+    handleCompanyUserChange(userId) {
+      if (!this.changeCompanyUserForm.companyId) {
+        this.$message.warning('请先选择公司');
+        this.changeCompanyUserForm.companyUserId = null;
+        return;
+      }
+    },
+    /** 重置更换会员归属表单 */
+    resetCompanyUserForm() {
+      this.changeCompanyUserForm = {
+        companyId: null,
+        companyUserId: null,
+        userIds: []
+      };
+      this.resetForm("changeCompanyUserForm");
+    },
+    handleCompanyChange(companyId) {
+      // 清空已选择的销售
+      this.changeCompanyUserForm.companyUserId = null;
+      // 根据公司ID获取对应的销售列表
+      if (companyId) {
+        getCompanyUserList({ companyId: companyId }).then(response => {
+          if (response.code === 200) {
+            this.companyUserOptions = response.data;
+          } else {
+            this.$message.error(response.msg || '获取销售列表失败');
+            this.companyUserOptions = [];
+          }
+        }).catch(() => {
+          this.$message.error('获取销售列表失败');
+          this.companyUserOptions = [];
+        });
+      } else {
+        this.companyUserOptions = [];
+      }
+    },
+    /** 取消更换会员归属 */
+    cancelChangeCompanyUser() {
+      this.changeCompanyUserOpen = false;
+      this.resetCompanyUserForm();
+    },
+    /** 提交更换会员归属 */
+    submitChangeCompanyUserForm() {
+      this.$refs["changeCompanyUserForm"].validate(valid => {
+        if (valid) {
+          // 调用更换会员归属接口
+          // 检查companyId是否已设置
+          if (!this.changeCompanyUserForm.companyId) {
+            this.$message.error('请选择公司');
+            return;
+          }
+
+          changeCompanyUser(this.selectedUser, {
+            companyUserId: this.changeCompanyUserForm.companyUserId,
+            companyId: this.changeCompanyUserForm.companyId
+          }).then(response => {
+            if (response.code === 200) {
+              this.msgSuccess("操作成功");
+              this.changeCompanyUserOpen = false;
+              this.getList();
+            } else {
+              this.$message.error(response.msg || '操作失败');
+            }
+          }).catch(() => {
+            this.$message.error('操作失败');
+          });
+        }
+      });
+    },
+    /** 获取项目对应名称 */
+    getProjectLabel(projectId) {
+      return this.projectOptions.find(item => parseInt(item.dictValue) === projectId)?.dictLabel;
+    },
+  }
+};
+
+
+</script>

+ 6 - 100
src/views/system/config/config.vue

@@ -521,92 +521,7 @@
         </div>
     </el-tab-pane>
     <el-tab-pane label="积分配置" name="his.integral">
-        <el-form ref="form11" :model="form11" :rules="rules3" label-width="180px">
-          <el-row>
-            <el-col :span="12">
-              <el-form-item  label="新手任务积分" prop="integralNewTask">
-                <el-tooltip class="item" effect="dark" content="首次app内下单公域疗法,赠送多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralNewTask"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item  label="购买消费" prop="integralRatio">
-                <el-tooltip class="item" effect="dark" content="实付多少元得1积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralRatio"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-          </el-row>
-          <el-row>
-            <el-col :span="12">
-              <el-form-item  label="分享获得积分" prop="integralShare">
-                <el-tooltip class="item" effect="dark" content="分享获得多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralShare"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item  label="完成随访获得积分" prop="integralFollow">
-                <el-tooltip class="item" effect="dark" content="完成随访获得多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralFollow"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-          </el-row>
-
-          <el-row>
-            <el-col :span="12">
-              <el-form-item  label="观看课程获得积分" prop="integralCourse">
-                <el-tooltip class="item" effect="dark" content="每小节观看>=50%获得多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralCourse"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item  label="浏览商品获得积分" prop="integralProduct">
-                <el-tooltip class="item" effect="dark" content="浏览商品每30秒获得多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralProduct"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-          </el-row>
-          <el-row>
-            <el-col :span="12">
-              <el-form-item  label="短视频/直播 获得积分" prop="integralFirstVideo">
-                <el-tooltip class="item" effect="dark" content="首次浏览每10秒获得多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralFirstVideo"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item  label="短视频/直播 获得积分" prop="integralFinishVideo">
-                <el-tooltip class="item" effect="dark" content="每10秒获得最低多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralFinishVideo"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-          </el-row>
-          <el-form-item  label="单日可获总积分" prop="integralByOneDay">
-            <el-tooltip class="item" effect="dark" content="每天最多可获得多少积分" placement="top-end">
-              <el-input-number  v-model="form11.integralByOneDay"   ></el-input-number>
-            </el-tooltip>
-          </el-form-item>
-          <el-form-item label="单日可获总积分类型" prop="integralTypeByOneDay">
-            <el-select v-model="form11.integralTypeByOneDay" multiple placeholder="请选择类型" filterable clearable size="small">
-              <el-option
-                v-for="dict in integralLogTypeOptions"
-                :key="dict.dictValue"
-                :label="dict.dictLabel"
-                :value="dict.dictValue"
-              />
-            </el-select>
-          </el-form-item>
-
-         <div   class="footer">
-            <el-button type="primary" @click="submitForm11">提  交</el-button>
-          </div>
-        </el-form>
+      <IntegralConfig/>
     </el-tab-pane>
     <el-tab-pane label="签到配置" name="his.sign">
     <el-table border :data="form12">
@@ -685,7 +600,7 @@
 
 
     <el-tab-pane label="系统配置" name="his.config">
-     <el-form ref="form11" :model="form13"  label-width="160px">
+     <el-form ref="form13" :model="form13"  label-width="160px">
       <el-form-item   label="腾讯云sdkAppId" prop="sdkAppId">
           <el-input   v-model="form13.sdkAppId"  label="请输入sdkAppId"></el-input>
       </el-form-item>
@@ -1497,14 +1412,15 @@ import {getAllFollowTempName } from "@/api/his/followTemp";
 import productAttrValueSelect from "../../components/his/productGiftValueSelect.vue";
 import productDeliveryGiftValueSelect from "../../components/his/productDeliveryGiftValueSelect.vue";
 import { Col } from "element-ui";
-import Editor from '@/components/Editor/wang';
 import companyMenuConfig from "./companyMenuConfig";
+import IntegralConfig from '@/views/system/config/integralConfig.vue'
 import {getCitys} from "@/api/store/city";
 export default {
   name: "Config",
   components: {
     companyMenuConfig,
-    Material,productAttrValueSelect,productDeliveryGiftValueSelect,Editor
+    Material,productAttrValueSelect,productDeliveryGiftValueSelect,
+    IntegralConfig
   },
   data() {
     return {
@@ -1546,8 +1462,6 @@ export default {
       form9:{
       },
       form10:[],
-      form11:{
-      },
       form12:[],
       form13:{
         dfAccounts: [] // 初始化代付管家账户数组
@@ -1821,7 +1735,7 @@ export default {
               this.form10 =JSON.parse(response.data.configValue);
           }
           if(key=="his.integral"){
-              this.form11 =JSON.parse(response.data.configValue);
+            return
           }
           if(key=="his.sign"){
               this.form12 =JSON.parse(response.data.configValue);
@@ -1966,14 +1880,6 @@ export default {
        }
      });
   },
-  submitForm11(){
-    var param={configId:this.configId,configValue:JSON.stringify(this.form11)}
-    updateConfigByKey(param).then(response => {
-       if (response.code === 200) {
-         this.msgSuccess("修改成功");
-       }
-     });
-  },
   submitForm12(){
     var param={configId:this.configId,configValue:JSON.stringify(this.form12)}
     updateConfigByKey(param).then(response => {

+ 221 - 0
src/views/system/config/integralConfig.vue

@@ -0,0 +1,221 @@
+<template>
+  <div class="app-container">
+    <el-form ref="form11" :model="form11" label-width="180px">
+      <el-row>
+        <el-col :span="12">
+          <el-form-item  label="注册任务积分" prop="integralRegister">
+            <el-tooltip class="item" effect="dark" content="完成手机号绑定注册,赠送多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralRegister"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item  label="完成专家咨询获得积分" prop="integralFinishConsultation">
+            <el-tooltip class="item" effect="dark" content="首次完成专家咨询,赠送多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralFinishConsultation"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item  label="填写就诊人信息获得积分" prop="integralAddPatient">
+            <el-tooltip class="item" effect="dark" content="首次填写就诊人信息,赠送多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralAddPatient"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item  label="填写收货地址获得积分" prop="integralAddUserAddress">
+            <el-tooltip class="item" effect="dark" content="首次填写收货地址,赠送多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralAddUserAddress"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item  label="邀请新用户获得积分" prop="integralInvite">
+            <el-tooltip class="item" effect="dark" content="邀请新用户,赠送多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralInvite"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item  label="被邀请获得积分" prop="integralInvited">
+            <el-tooltip class="item" effect="dark" content="填写用户邀请码,赠送多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralInvited"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item  label="新手任务积分" prop="integralNewTask">
+            <el-tooltip class="item" effect="dark" content="首次app内下单公域疗法,赠送多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralNewTask"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item  label="购买消费获得" prop="integralRatio">
+            <el-tooltip class="item" effect="dark" content="实付1元得多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralRatio"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item  label="分享获得积分" prop="integralShare">
+            <el-tooltip class="item" effect="dark" content="分享获得多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralShare"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item  label="完成随访获得积分" prop="integralFollow">
+            <el-tooltip class="item" effect="dark" content="完成随访获得多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralFollow"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="12">
+          <el-form-item  label="观看课程获得积分" prop="integralCourse">
+            <el-tooltip class="item" effect="dark" content="每小节观看>=90%获得多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralCourse"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item  label="浏览商品获得积分" prop="integralProduct">
+            <el-tooltip class="item" effect="dark" content="浏览商品每30秒获得多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralProduct"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item  label="短视频/直播 获得积分" prop="integralFirstVideo">
+            <el-tooltip class="item" effect="dark" content="首次浏览每10秒获得多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralFirstVideo"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item  label="短视频/直播 获得积分" prop="integralFinishVideo">
+            <el-tooltip class="item" effect="dark" content="每10秒获得最低多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralFinishVideo"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item  label="付费课程订阅" prop="integralSubscriptCourse">
+            <el-tooltip class="item" effect="dark" content="实付1元得多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralSubscriptCourse"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item  label="首单立返积分" prop="integralFirstOrderPoint">
+            <el-tooltip class="item" effect="dark" content="首次在积分商城下单,返多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralFirstOrderPoint"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item  label="单日可获总积分" prop="integralByOneDay">
+            <el-tooltip class="item" effect="dark" content="每天最多可获得多少积分" placement="top-end">
+              <el-input-number  v-model="form11.integralByOneDay"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-form-item label="单日可获总积分类型" prop="integralTypeByOneDay">
+        <el-select v-model="form11.integralTypeByOneDay" multiple placeholder="请选择类型" filterable clearable size="small">
+          <el-option
+            v-for="dict in integralLogTypeOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <div style="display: flex; justify-content: flex-end;">
+        <el-button type="primary" :disabled="saveLoading" :loading="saveLoading" @click="submitForm11">提  交</el-button>
+      </div>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import { getConfigByKey, updateConfigByKey } from '@/api/system/config'
+
+export default {
+  name: "IntegralConfig",
+  data() {
+    return {
+      integralLogTypeOptions: [],
+      form11: {
+        integralRegister: null,
+        integralFinishConsultation: null,
+        integralAddPatient: null,
+        integralAddUserAddress: null,
+        integralInvite: null,
+        integralInvited: null,
+        integralNewTask: null,
+        integralRatio: null,
+        integralShare: null,
+        integralFollow: null,
+        integralCourse: null,
+        integralProduct: null,
+        integralFirstVideo: null,
+        integralFinishVideo: null,
+        integralByOneDay: null,
+        integralFirstOrderPoint: null,
+        integralTypeByOneDay: null,
+        integralSubscriptCourse: null,
+      },
+      saveLoading: false,
+    }
+  },
+  created() {
+    this.getConfigByKey("his.integral");
+    this.getDicts("sys_integral_log_type").then(response => {
+      this.integralLogTypeOptions = response.data;
+    });
+  },
+  methods: {
+    getConfigByKey(key){
+      getConfigByKey(key).then(response => {
+        this.configId=response.data.configId;
+        this.configKey=response.data.configKey;
+        this.form11 =JSON.parse(response.data.configValue);
+      });
+    },
+    submitForm11(){
+      this.saveLoading = true
+      const param={configId:this.configId,configValue:JSON.stringify(this.form11)}
+      updateConfigByKey(param).then(response => {
+        const {code} = response
+        if (code === 200) {
+          this.msgSuccess("修改成功");
+        }
+        this.saveLoading = false
+      });
+    },
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 23 - 22
src/views/user/darkRoom/index.vue

@@ -17,21 +17,22 @@
       </el-form-item>
     </el-form>
 
-    <el-row :gutter="10" class="mb8">
-      <el-button
-        size="mini"
-        type="primary"
-        style="margin-left: 5px"
-        :disabled="ids.length === 0"
-        @click="handleUpdateBatch"
-        v-hasPermi="['his:user:enabledUsers']"
-      >批量启用</el-button>
-      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
+<!--    <el-row :gutter="10" class="mb8">-->
+<!--      <el-button-->
+<!--        size="mini"-->
+<!--        type="primary"-->
+<!--        style="margin-left: 5px"-->
+<!--        :disabled="ids.length === 0"-->
+<!--        @click="handleUpdateBatch"-->
+<!--        v-hasPermi="['his:user:enabledUsers']"-->
+<!--      >批量启用</el-button>-->
+<!--      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>-->
+<!--    </el-row>-->
 
     <el-table  height="500" border v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="用户id" align="center" prop="userId" />
+      <el-table-column label="项目" align="center" prop="projectName" />
       <el-table-column label="用户昵称" align="center" prop="nickname" />
       <el-table-column label="用户头像" align="center" prop="avatar">
         <template slot-scope="scope">
@@ -67,16 +68,16 @@
       <el-table-column label="标签名称" align="center" prop="tag" />
       <el-table-column label="所属销售名称" align="center" prop="companyUserNickName" width="120px"/>
       <el-table-column label="备注" align="center" prop="remark" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-        <template slot-scope="scope">
-          <el-button
-            size="mini"
-            type="text"
-            @click="handleUpdate(scope.row)"
-            v-hasPermi="['his:user:darkRoomList']"
-          >启用</el-button>
-        </template>
-      </el-table-column>
+<!--      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">-->
+<!--        <template slot-scope="scope">-->
+<!--          <el-button-->
+<!--            size="mini"-->
+<!--            type="text"-->
+<!--            @click="handleUpdate(scope.row)"-->
+<!--            v-hasPermi="['his:user:darkRoomList']"-->
+<!--          >启用</el-button>-->
+<!--        </template>-->
+<!--      </el-table-column>-->
     </el-table>
 
     <pagination
@@ -115,7 +116,7 @@ export default {
         pageNum: 1,
         pageSize: 10,
         keyword: null,
-        isBlack: true
+        status: 0
       },
     };
   },

+ 16 - 3
src/views/user/transfer/index.vue

@@ -163,6 +163,11 @@
         <el-form-item label="转移矩阵" prop="customerIds">
           <el-table :data="form.customerList" border>
             <el-table-column label="客户" align="center" prop="userName" />
+            <el-table-column label="项目" align="center" prop="projectId">
+              <template slot-scope="scope">
+                <el-tag v-if="scope.row.projectId">{{ getProjectLabel(scope.row.projectId) }}</el-tag>
+              </template>
+            </el-table-column>
             <el-table-column label="转移前销售" align="center" prop="beforeCompanyUserName" />
             <el-table-column label="转移后销售" align="center" prop="afterCompanyUserName" />
           </el-table>
@@ -182,7 +187,7 @@
           </el-radio-group>
         </el-form-item>
         <el-form-item label="审批意见" prop="approvalRemark">
-          <el-input v-model="form.approvalRemark" type="textarea" placeholder="请输入内容"  disabled="disabled"/>
+          <el-input v-model="form.approvalRemark" type="textarea" placeholder="请输入内容"  :disabled="viewMode"/>
         </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
@@ -281,7 +286,8 @@ export default {
         approvalRemark: [
           { required: true, message: "审批意见不能为空!", trigger: "blur" }
         ]
-      }
+      },
+      projectOptions: [],
     };
   },
   created() {
@@ -291,6 +297,9 @@ export default {
     getDicts('transfer_approval_status').then(response => {
       this.approvalStatusList = response.data;
     })
+    this.getDicts("sys_course_project").then(response => {
+      this.projectOptions = response.data;
+    });
     getAllUserListLimit().then(res=>{
       if(res.code === 200) {
         this.companyUserList = res.data
@@ -432,7 +441,11 @@ export default {
         }).then(response => {
           this.download(response.msg);
         }).catch(function() {});
-    }
+    },
+    /** 获取项目对应名称 */
+    getProjectLabel(projectId) {
+      return this.projectOptions.find(item => parseInt(item.dictValue) === projectId)?.dictLabel;
+    },
   }
 };
 </script>