yfh vor 11 Stunden
Ursprung
Commit
4c6bcbe0e1
48 geänderte Dateien mit 5000 neuen und 471 gelöschten Zeilen
  1. 9 1
      src/api/common.js
  2. 27 0
      src/api/company/companyUser.js
  3. 9 0
      src/api/company/redPacket.js
  4. 9 0
      src/api/course/userCourseVideo.js
  5. 9 0
      src/api/qw/externalContact.js
  6. 17 0
      src/api/qw/sopTemp.js
  7. 53 0
      src/api/tag/api.js
  8. 18 0
      src/api/wechat.js
  9. 96 0
      src/utils/cos.js
  10. 112 0
      src/utils/liveWS.js
  11. 73 0
      src/utils/obs.js
  12. 15 0
      src/utils/util.js
  13. 12 6
      src/views/company/companyRole/index.vue
  14. 18 2
      src/views/company/companyUser/index.vue
  15. 59 2
      src/views/company/companyUser/profile/index.vue
  16. 74 26
      src/views/components/course/userCourseCatalogDetails.vue
  17. 504 0
      src/views/components/tag/AutoTagDialog.vue
  18. 10 2
      src/views/course/courseUserStatistics/qw/index.vue
  19. 116 5
      src/views/course/courseUserStatistics/qw/my.vue
  20. 87 7
      src/views/course/courseWatchLog/deptWatchLog.vue
  21. 81 1
      src/views/course/courseWatchLog/index.vue
  22. 61 1
      src/views/course/courseWatchLog/qw/statistics.vue
  23. 230 14
      src/views/course/courseWatchLog/watchLog.vue
  24. 0 7
      src/views/crm/customer/full.vue
  25. 43 4
      src/views/index.vue
  26. 1 1
      src/views/qw/contactWay/group.vue
  27. 141 19
      src/views/qw/externalContact/deptIndex.vue
  28. 140 28
      src/views/qw/externalContact/index.vue
  29. 125 4
      src/views/qw/externalContact/myExternalContact.vue
  30. 10 1
      src/views/qw/externalContactLoss/deptLossIndex.vue
  31. 10 1
      src/views/qw/externalContactLoss/index.vue
  32. 10 0
      src/views/qw/externalContactLoss/my.vue
  33. 92 67
      src/views/qw/friendWelcome/indexNew.vue
  34. 92 66
      src/views/qw/friendWelcome/myWelcome.vue
  35. 102 17
      src/views/qw/sop/addSop.vue
  36. 123 40
      src/views/qw/sop/deptSop.vue
  37. 110 28
      src/views/qw/sop/mySop.vue
  38. 2 1
      src/views/qw/sop/sop.vue
  39. 95 28
      src/views/qw/sop/updateSop.vue
  40. 932 0
      src/views/qw/sopTemp/deptIndex.vue
  41. 102 2
      src/views/qw/sopTemp/index.vue
  42. 830 0
      src/views/qw/sopTemp/myIndex.vue
  43. 35 13
      src/views/qw/sopTemp/sopTemp.vue
  44. 184 74
      src/views/qw/sopTemp/updateSopTemp.vue
  45. 17 1
      src/views/qw/sopUserLogs/sopUserLogsSchedule.vue
  46. 60 1
      src/views/qw/user/cuDeptIdIndex.vue
  47. 24 1
      src/views/qw/user/index.vue
  48. 21 0
      src/views/qw/user/myIndex.vue

+ 9 - 1
src/api/common.js

@@ -14,4 +14,12 @@ export function getTask(taskId) {
     method: 'get'
   })
 }
- 
+
+export function getTmpSecretKey(query) {
+  return request({
+    url: '/common/getTmpSecretKey',
+    method: 'get',
+    params:query
+  })
+
+}

+ 27 - 0
src/api/company/companyUser.js

@@ -43,6 +43,23 @@ export function getQwAllUserList(id) {
   })
 }
 
+
+export function getQwMyUserList(id) {
+  return request({
+    url: '/company/user/getQwMyUserList/'+id,
+    method: 'get'
+  })
+}
+
+export function getQwDeptUserList(id) {
+  return request({
+    url: '/company/user/getQwDeptUserList/'+id,
+    method: 'get'
+  })
+}
+
+
+
 export function getUserListByDeptId(query) {
   return request({
     url: '/company/user/getUserListByDeptId',
@@ -235,6 +252,16 @@ export function getCompanyUserListLikeName(params) {
   })
 }
 
+// 根据名模糊查询用户列表
+export function getCompanyUserListLikeNameByDept(params) {
+  return request({
+    url: '/company/user/getCompanyUserListLikeNameDept',
+    method: 'get',
+    params: params
+  })
+}
+
+
 // 查询企业员工信息列表
 export function listCompanyUser(query) {
   return request({

+ 9 - 0
src/api/company/redPacket.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function redPacketInfo(query) {
+  return request({
+    url: '/his/redPacket/info',
+    method: 'get',
+    params: query
+  })
+}

+ 9 - 0
src/api/course/userCourseVideo.js

@@ -39,6 +39,15 @@ export function addUserCourseVideo(data) {
   })
 }
 
+// 更新课程小节
+export function updateUserCourseVideoUpdate(data) {
+  return request({
+    url: '/course/userCourseVideo/update',
+    method: 'post',
+    data: data
+  })
+}
+
 // 修改课堂视频
 export function updateUserCourseVideo(data) {
   return request({

+ 9 - 0
src/api/qw/externalContact.js

@@ -306,6 +306,15 @@ export function batchUpdateExternalContactNotes(data) {
   })
 }
 
+export function batchUpdateExternalContactNotesByWatchLog(data) {
+  return request({
+    url: '/qw/externalContact/batchUpdateExternalContactNotesByWatchLog',
+    method: 'post',
+    data: data
+  })
+}
+
+
 // 查询企业微信客户流失删除统计列表
 export function delLossStatistics(query) {
   return request({

+ 17 - 0
src/api/qw/sopTemp.js

@@ -8,6 +8,23 @@ export function listSopTemp(query) {
     params: query
   })
 }
+
+export function listSopTempMyList(query) {
+  return request({
+    url: '/qw/sopTemp/myList',
+    method: 'get',
+    params: query
+  })
+}
+
+export function listSopTempDeptList(query) {
+  return request({
+    url: '/qw/sopTemp/deptList',
+    method: 'get',
+    params: query
+  })
+}
+
 // 查询sop模板列表
 export function redList(id) {
   return request({

+ 53 - 0
src/api/tag/api.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询视频小节看课标签关联列表
+export function listTag(query) {
+  return request({
+    url: '/shop/tag/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询视频小节看课标签关联详细
+export function getTag(id) {
+  return request({
+    url: '/shop/tag/' + id,
+    method: 'get'
+  })
+}
+
+// 新增视频小节看课标签关联
+export function addTag(data) {
+  return request({
+    url: '/shop/tag',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改视频小节看课标签关联
+export function updateTag(data) {
+  return request({
+    url: '/shop/tag',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除视频小节看课标签关联
+export function delTag(id) {
+  return request({
+    url: '/shop/tag/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出视频小节看课标签关联
+export function exportTag(query) {
+  return request({
+    url: '/shop/tag/export',
+    method: 'get',
+    params: query
+  })
+}

+ 18 - 0
src/api/wechat.js

@@ -0,0 +1,18 @@
+import request from '@/utils/request'
+
+// 获取绑定二维码
+export function getWechatBindQrcode() {
+  return request({
+    url: '/wechat/bind/qrcode',
+    method: 'get'
+  });
+}
+
+// 检测绑定状态
+export function checkWechatBindStatus(sceneId) {
+  return request({
+    url: '/wechat/bind/status',
+    method: 'get',
+    params: { sceneId }
+  });
+}

+ 96 - 0
src/utils/cos.js

@@ -0,0 +1,96 @@
+import COS from 'cos-js-sdk-v5';
+import { Message } from 'element-ui';
+import { getTmpSecretKey } from '@/api/common';
+
+console.log('环境变量:', process.env);
+console.log('NODE_ENV:', process.env.NODE_ENV);
+console.log('VUE_APP_COS_BUCKET:', process.env.VUE_APP_COS_BUCKET);
+console.log('VUE_APP_COS_REGION:', process.env.VUE_APP_COS_REGION);
+
+const config = {
+  Bucket: process.env.VUE_APP_COS_BUCKET,
+  Region: process.env.VUE_APP_COS_REGION,
+};
+console.log('COS配置:', config);
+
+// 上传到腾讯云cos
+export const uploadObject = async (file,onProgress,type,callBackUp) => {
+    try {
+        console.log(type);
+        const response = await getTmpSecretKey(); // 后台接口返回 密钥相关信息
+        console.log("Key  ",response);
+        const data = response.data;
+        const credentials = data && data.credentials;
+
+        if (!data || !credentials) {
+            console.error('未获取到参数');
+            return;
+        }
+
+        // 初始化
+        const cos = new COS({
+            getAuthorization: (options, callback) => {
+                callback({
+                    TmpSecretId: credentials.tmpSecretId,
+                    TmpSecretKey: credentials.tmpSecretKey,
+                    XCosSecurityToken: credentials.sessionToken,
+                    StartTime: data.startTime,
+                    ExpiredTime: data.expiredTime,
+                });
+            },
+        });
+
+        console.log("初始化成功")
+        let fileName = file.name || ""
+        const upload_file_name = new Date().getTime() + '.' + fileName.split(".")[fileName.split(".").length - 1];
+        let date =  new Date()
+        let year = date.getFullYear()
+        let month = date.getMonth() + 1
+        let strDate = date.getDate()
+        let uploadDay = `${year}${month}${strDate}`
+        let videoKey = `/userVideo/${uploadDay}/${upload_file_name}`
+        let courseKey = `/live/${uploadDay}/${upload_file_name}`
+        let key = type ===1 ? courseKey : videoKey;
+        console.log("开始上传")
+        return new Promise((resolve, reject) => {
+            console.log("uploadFile")
+            cos.uploadFile(
+                {
+                    Bucket: config.Bucket, /* 必须 */
+                    Region: config.Region, /* 存储桶所在地域,必须字段 */
+                    Key: key, // 文件名
+                    StorageClass: 'STANDARD', // 上传类型,可选
+                    Body: file, // 上传文件对象
+                    // onTaskReady: function (taskId) {
+                    //     // 用于中断分片上传回调
+                    //     console.log('Task ready:', taskId);
+                    //     callBackUp && callBackUp({cos,taskId})
+                    // },
+                    onProgress: function (progressData) {
+                        console.log('COS上传进度=======>:', JSON.stringify(progressData));
+                        onProgress(progressData);
+                    },
+                    // onFileFinish: function (err, data, options) {
+                    //     console.log(options.Key + '上传' + (err ? '失败' : '完成'));
+                    // },
+                },
+                (err, data) => {
+                    if (err) {
+                        reject(err);
+                    } else {
+                        // 将上传的key包含在返回的数据中
+                        const result = {
+                            ...data,
+                            urlPath: key
+                        };
+                        console.log('上传成功', result);
+                        resolve(result);
+                    }
+                }
+            );
+        });
+    } catch (error) {
+        console.error('Error during upload:', error);
+        throw error;
+    }
+};

+ 112 - 0
src/utils/liveWS.js

@@ -0,0 +1,112 @@
+import CryptoJS from 'crypto-js'
+
+export class LiveWS {
+  /**
+   * @param {string} url - WebSocket 服务器地址
+   * @param {number} liveId - 直播间ID
+   * @param {number} userId - 用户ID
+   * @param {number} checkInterval - 检查连接状态的时间间隔,单位毫秒
+   * @param {number} reconnectDelay - 连接断开后重连的延迟,单位毫秒
+   */
+  constructor(url, liveId, userId, checkInterval = 5000, reconnectDelay = 3000) {
+    let timestamp = new Date().getTime()
+    let userType = 1
+    let signature = CryptoJS.HmacSHA256(
+      CryptoJS.enc.Utf8.parse('' + liveId + userId + userType + timestamp),
+      CryptoJS.enc.Utf8.parse(timestamp)).toString(CryptoJS.enc.Hex)
+    this.url = url + `?liveId=${liveId}&userId=${userId}&userType=${userType}&timestamp=${timestamp}&signature=${signature}`;
+    console.log(this.url)
+    this.liveId = liveId;
+    this.userId = userId;
+    this.checkInterval = checkInterval;
+    this.reconnectDelay = reconnectDelay;
+    this.ws = null;
+    this.reconnectTimer = null;
+    this.heartbeatTimer = null;
+    this.isManualClose = false;
+    this.connect();
+    this.startHeartbeat();
+  }
+
+  connect() {
+    // 如果已经有一个连接处于 OPEN 或 CONNECTING 状态,则不再创建新连接
+    if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
+      return;
+    }
+
+    this.ws = new WebSocket(this.url);
+
+    // 绑定事件
+    this.ws.onopen = (event) => {
+      // 连接成功后,清除重连计时器
+      if (this.reconnectTimer) {
+        clearTimeout(this.reconnectTimer);
+        this.reconnectTimer = null;
+      }
+    };
+
+    this.ws.onmessage = (event) => {
+      this.onmessage(event);
+    };
+
+    this.ws.onerror = (error) => {
+    };
+
+    this.ws.onclose = (event) => {
+      // 如果不是主动关闭,则重连
+      if (!this.isManualClose) {
+        setTimeout(() => this.reconnect(), this.reconnectDelay);
+      }
+    };
+  }
+
+  onmessage(event) {}
+
+  reconnect() {
+    this.connect();
+  }
+
+  // 调度重连
+  scheduleReconnect() {
+    // 避免多次重连定时器同时存在
+    if (this.reconnectTimer) return;
+    this.reconnectTimer = setTimeout(() => {
+      this.connect();
+    }, this.reconnectDelay);
+  }
+
+  // 定时检查连接状态
+  startHeartbeat() {
+    this.heartbeatTimer = setInterval(() => {
+      if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
+        this.scheduleReconnect();
+      } else {
+        // 发送信息
+        this.ws.send(JSON.stringify({'cmd':'heartbeat','msg':'ping', 'liveId': this.liveId, 'userId': this.userId}));
+      }
+    }, this.checkInterval);
+  }
+
+  // 主动关闭 WebSocket 连接,并清除定时任务
+  close() {
+    this.isManualClose = true;
+    if (this.heartbeatTimer) {
+      clearInterval(this.heartbeatTimer);
+    }
+    if (this.reconnectTimer) {
+      clearTimeout(this.reconnectTimer);
+    }
+    if (this.ws) {
+      this.ws.close();
+    }
+  }
+
+  // 发送消息方法
+  send(message) {
+    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
+      this.ws.send(message);
+    } else {
+      console.error("WebSocket is not open. Message not sent.");
+    }
+  }
+}

+ 73 - 0
src/utils/obs.js

@@ -0,0 +1,73 @@
+import ObsClient from "esdk-obs-browserjs/src/obs"
+
+export const uploadToOBS = async (file, progressCallback, type, cancelCallback) => {
+  try {
+    const obsClient = new ObsClient({
+      access_key_id: process.env.VUE_APP_OBS_ACCESS_KEY_ID,
+      secret_access_key: process.env.VUE_APP_OBS_SECRET_ACCESS_KEY,
+      server: process.env.VUE_APP_OBS_SERVER,
+      timeout: 1200,
+    })
+
+    const fileName = file.name || ""
+    const upload_file_name = new Date().getTime() + "." + fileName.split(".")[fileName.split(".").length - 1]
+    const date = new Date()
+    const year = date.getFullYear()
+    const month = date.getMonth() + 1
+    const strDate = date.getDate()
+    const uploadDay = `${year}${month}${strDate}`
+    const videoKey = `userVideo/${uploadDay}/${upload_file_name}`
+    const courseKey = `course/${uploadDay}/${upload_file_name}`
+    const key = type === 1 ? courseKey : videoKey
+
+    var callback = (transferredAmount, totalAmount, totalSeconds) => {
+      const progress = Number.parseInt((transferredAmount * 100.0) / totalAmount)
+      if (progressCallback) {
+        progressCallback(progress)
+      }
+    }
+
+    return new Promise((resolve, reject) => {
+      let isCancelled = false
+
+      if (cancelCallback) {
+        cancelCallback({
+          cancel: () => {
+            console.log("Cancelling OBS upload")
+            isCancelled = true
+            reject(new Error("Upload cancelled by user"))
+          },
+        })
+      }
+
+      obsClient.putObject(
+        {
+          Bucket: process.env.VUE_APP_OBS_BUCKET,
+          Key: key,
+          Body: file,
+          ProgressCallback: callback,
+        },
+        (err, result) => {
+          if (isCancelled) {
+            return // Don't process result if cancelled
+          }
+
+          if (err) {
+            reject(err)
+            console.error("Error-->" + err)
+          } else {
+            const a = {
+              ...result,
+              urlPath: key,
+            }
+            console.log("上传成功", a)
+            resolve(a)
+          }
+        },
+      )
+    })
+  } catch (error) {
+    console.error("Error during upload:", error)
+    throw error
+  }
+}

+ 15 - 0
src/utils/util.js

@@ -34,3 +34,18 @@ export function randomString(len) {
 }
 
 
+function isEmpty(str) {
+  // 检查是否为 null 或 undefined
+  if (str == null) return true;
+
+  // 检查是否为字符串类型
+  if (typeof str !== 'string') {
+    // 如果不是字符串,尝试转换为字符串再判断
+    return String(str).trim() === '';
+  }
+
+  // 字符串类型直接去除空格后判断
+  return str.trim() === '';
+}
+
+

+ 12 - 6
src/views/company/companyRole/index.vue

@@ -7,7 +7,7 @@
           placeholder="请输入角色名称"
           clearable
           size="small"
-          
+
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
@@ -17,7 +17,7 @@
           placeholder="请输入权限字符"
           clearable
           size="small"
-         
+
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
@@ -27,7 +27,7 @@
           placeholder="角色状态"
           clearable
           size="small"
-          
+
         >
           <el-option
             v-for="dict in statusOptions"
@@ -37,7 +37,7 @@
           />
         </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>
@@ -259,7 +259,7 @@ export default {
       menuNodeAll: false,
       deptExpand: true,
       deptNodeAll: false,
-   
+
       // 状态数据字典
       statusOptions: [],
       // 数据范围选项
@@ -524,6 +524,9 @@ export default {
                 this.open = false;
                 this.getList();
               }
+            }).finally(() => {
+              this.open = false;
+              this.getList();
             });
           } else {
             this.form.menuIds = this.getMenuAllCheckedKeys();
@@ -533,6 +536,9 @@ export default {
                 this.open = false;
                 this.getList();
               }
+            }).finally(() => {
+              this.open = false;
+              this.getList();
             });
           }
         }
@@ -580,4 +586,4 @@ export default {
     }
   }
 };
-</script>
+</script>

+ 18 - 2
src/views/company/companyUser/index.vue

@@ -1342,9 +1342,25 @@ export default {
     },
     /** 删除按钮操作 */
     handleDelete(row) {
+
       const userIds = row.userId || this.ids;
+
+      // 筛选出 userType 为 '00' 的 userId
+      const excludedUserIds = this.userList
+        .filter(user => user.userType == '00')
+        .map(user => user.userId);
+
+      // 从 userIds 中剔除这些 userId
+      const finalUserIds = userIds.filter(userId =>
+        !excludedUserIds.includes(userId)
+      );
+
+      if (finalUserIds.length === 0) {
+        return this.msgInfo("除管理员外无其他的账号,请重新选择");
+      }
+
       this.$confirm(
-        '是否确认删除员工编号为"' + userIds + '"的数据项?【注意:删除后绑定的企业微信相关信息可能会错乱!!】',
+        '是否确认删除员工编号为"' + finalUserIds + '"的数据项?【注意:删除后绑定的企业微信相关信息可能会错乱!!】',
         "警告",
         {
           confirmButtonText: "确定",
@@ -1353,7 +1369,7 @@ export default {
         }
       )
         .then(function() {
-          return delUser(userIds);
+          return delUser(finalUserIds);
         })
         .then(() => {
           this.getList();

+ 59 - 2
src/views/company/companyUser/profile/index.vue

@@ -36,6 +36,16 @@
                 <div class="pull-right">{{ user.createTime }}</div>
               </li>
             </ul>
+            <div class="text-center" style="margin-top: 20px;" v-if="needWxTemplateMsg">
+              <div v-if="user.wechatBindStatus">
+                <el-tag type="success">已绑定服务号通知</el-tag>
+              </div>
+              <div v-else>
+                <el-button type="primary" @click="openWechatBindDialog">
+                  订阅服务号通知
+                </el-button>
+              </div>
+            </div>
           </div>
         </el-card>
       </el-col>
@@ -55,6 +65,20 @@
         </el-card>
       </el-col>
     </el-row>
+    <el-dialog
+      title="订阅服务号通知"
+      :visible.sync="bindDialogVisible"
+      width="360px"
+      :close-on-click-modal="false"
+    >
+      <div class="text-center">
+        <img v-if="wechatQrcode" :src="wechatQrcode" style="width: 260px; height: 260px;" />
+        <p style="margin-top: 10px;">请使用微信扫码绑定服务号通知</p>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="bindDialogVisible = false">关闭</el-button>
+      </span>
+    </el-dialog>
   </div>
 </template>
 
@@ -63,7 +87,7 @@ import userAvatar from "./userAvatar";
 import userInfo from "./userInfo";
 import resetPwd from "./resetPwd";
 import { getUserProfile } from "@/api/company/companyUser";
-
+import { getWechatBindQrcode, checkWechatBindStatus } from "@/api/wechat";
 export default {
   name: "Profile",
   components: { userAvatar, userInfo, resetPwd },
@@ -72,10 +96,17 @@ export default {
       user: {},
       roleGroup: {},
       postGroup: {},
-      activeTab: "userinfo"
+      activeTab: "userinfo",
+      bindDialogVisible: false,
+      wechatQrcode: "",
+      wechatBindTimer: null,
+      needWxTemplateMsg: false,
     };
   },
   created() {
+    if (process.env.VUE_APP_COMPANY_NAME ==="河北红德堂医药连锁有限公司保定第五十七分公司"){
+      this.needWxTemplateMsg = true
+    }
     this.getUser();
   },
   methods: {
@@ -85,7 +116,33 @@ export default {
         this.roleGroup = response.roleGroup;
         this.postGroup = response.postGroup;
       });
+    },
+    openWechatBindDialog() {
+      getWechatBindQrcode().then(res => {
+        console.log(res)
+        this.wechatQrcode = res.data.qrcodeUrl;
+        this.bindDialogVisible = true;
+        this.startCheckWechatBind(res.data.sceneId);
+      });
+    },
+    startCheckWechatBind(sceneId) {
+      // 清除旧定时器
+      if (this.wechatBindTimer) clearInterval(this.wechatBindTimer);
+
+      this.wechatBindTimer = setInterval(() => {
+        checkWechatBindStatus(sceneId).then(res => {
+          if (res.data === true) {
+            this.$message.success("绑定成功!");
+            clearInterval(this.wechatBindTimer);
+            this.bindDialogVisible = false;
+            this.getUser(); // 刷新绑定状态
+          }
+        });
+      }, 2000);
     }
+  },
+  beforeDestroy() {
+    if (this.wechatBindTimer) clearInterval(this.wechatBindTimer);
   }
 };
 </script>

+ 74 - 26
src/views/components/course/userCourseCatalogDetails.vue

@@ -33,7 +33,6 @@
       <el-table-column label="上传时间" align="center" prop="createTime" />
       <el-table-column label="默认红包" align="center" prop="redPacketMoney" />
       <el-table-column label="公司红包" align="center" prop="companyRedPacketMoney" />
-
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
           <el-button
@@ -49,12 +48,10 @@
             @click="updateMoney(scope.row)"
           >设置红包金额</el-button>
 
-<!--          <el-button-->
-<!--            size="mini"-->
-<!--            type="text"-->
-<!--            @click="openDialog(scope.row)"-->
-<!--            v-hasPermi="['course:courseLink:create']"-->
-<!--          >生成应急短链</el-button>-->
+          <el-button size="mini"
+                     type="text" @click="openTagDialog(scope.row)">
+            绑定看课标签
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -72,6 +69,7 @@
       :visible.sync="open" append-to-body>
       <userCourseVideoDetails  ref="userCourseVideoDetails" />
     </el-drawer>
+
     <el-dialog title="设置红包金额" :visible.sync="moneyOpen" width="600px" append-to-body>
       <el-form ref="form"  label-width="100px">
         <el-form-item label="红包金额" prop="corpId">
@@ -89,8 +87,7 @@
       :visible.sync="dialogVisible"
       width="400px"
       @close="resetForm"
-      append-to-body
-    >
+      append-to-body>
       <div>
         <p style="color: gray;">不传默认以系统参数为准</p>
         <el-form :model="linkForm" label-width="120px">
@@ -108,34 +105,55 @@
         <el-button type="primary" @click="confirm">确认</el-button>
       </div>
     </el-dialog>
-<!--    <el-dialog title="设置红包金额" :visible.sync="moneyOpen" width="600px" append-to-body>-->
-<!--      <el-form ref="form"  label-width="100px">-->
-<!--        <el-form-item label="红包金额" prop="corpId">-->
-<!--          <el-input-number v-model="redPacketMoneyForm.redPacketMoney" :min="0.1" :max="200" :step="0.1" ></el-input-number>-->
-<!--        </el-form-item>-->
-<!--      </el-form>-->
-<!--      <div slot="footer" class="dialog-footer">-->
-<!--        <el-button @click="cancel">取 消</el-button>-->
-<!--      </div>-->
-<!--    </el-dialog>-->
+
+    <AutoTagDialog
+      :visible.sync="tagDialogVisible"
+      :title="tagDialogTitle"
+      :videoId="currentVideoId"
+      append-to-body
+    />
+
   </div>
 </template>
 
 <script>
-import { updatePacketMoney,getSort,getVideoListByCourseId,delUserCourseVideo,getUserCourseVideo,addUserCourseVideo,updateUserCourseVideo } from "@/api/course/userCourseVideo";
+import {getVideoListByCourseId, updatePacketMoney, updateUserCourseVideoUpdate} from "@/api/course/userCourseVideo";
 import userCourseVideoDetails from '../../components/course/userCourseVideoDetails.vue';
-import { createLinkUrl } from "@/api/course/sopCourseLink";
-import { userList } from "@/api/qw/user";
-
-import request from '@/utils/request'
+import {createLinkUrl} from "@/api/course/sopCourseLink";
+import AutoTagDialog from "@/views/components/tag/AutoTagDialog.vue";
+import {addTag, updateTag} from "@/api/tag/api";
 
-  export default {
+export default {
     name: "userCourseCatalog",
     components: {
-      userCourseVideoDetails
+      userCourseVideoDetails,
+      AutoTagDialog
+    },
+    props: {
+      video: {
+        type: Object,
+        required: true,
+      },
     },
     data() {
       return {
+        currentRow: null,
+        // 假设这里有当前课程小节的数据传入,里面含id等
+        tagGroups: [],
+        tagsInGroup: [],
+        tagDialogVisible: false,
+        tagDialogTitle: "",
+        tagDialogFormData: null,
+        currentVideoId:null,
+        tagDialog: {
+          videoId: null,
+          groupId: null,
+          watchTagId: null,
+          finishTagId: null,
+          tgId: null,
+          watchingTgId: null,
+          watchedTgId: null
+        },
         linkForm:{
           days:null,
           courseId:null,
@@ -218,6 +236,36 @@ import request from '@/utils/request'
       });
     },
     methods: {
+
+      closeTagDialog(){
+        this.tagDialogVisible = false;
+      },
+      openTagDialog(row) {
+        this.currentRow = row;
+
+
+        this.tagDialogVisible = true;
+        this.currentVideoId = row.videoId;
+      },
+      updateTagsInGroup(groupId) {
+        let tagGroup = this.tagGroups.find(e=> e.groupId === groupId);
+
+        this.tagsInGroup = tagGroup.tag || [];
+        // 切换组时清空标签选择
+        this.tagDialog.watchTagId = null;
+        this.tagDialog.finishTagId = null;
+        this.tagDialog.tgId = tagGroup.id;
+      },
+      resetDialog() {
+        this.tagDialog = {
+          videoId: null,
+          groupId: null,
+          watchTagId: null,
+          finishTagId: null,
+        };
+        this.tagGroups = [];
+        this.tagsInGroup = [];
+      },
       // 打开弹框
       openDialog(row) {
         this.linkForm.courseId = row.courseId;

+ 504 - 0
src/views/components/tag/AutoTagDialog.vue

@@ -0,0 +1,504 @@
+<template>
+  <el-dialog :visible.sync="visible" title="标签绑定管理" width="1000px"
+   @close="handleClose"
+   append-to-body
+  >
+    <div class="app-container">
+      <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+        <el-form-item label="企微公司" prop="corpId">
+          <el-select v-model="queryParams.corpId" placeholder="企微公司"  size="small" @change="updateCorpId()">
+            <el-option
+              v-for="dict in myQwCompanyList"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="视频ID" prop="videoId">
+          <el-input
+            v-model="queryParams.videoId"
+            placeholder="请输入视频ID"
+            clearable
+            size="small"
+            @keyup.enter.native="handleQuery"
+            disabled
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+        <el-col :span="1.5">
+          <el-button
+            type="primary"
+            plain
+            icon="el-icon-plus"
+            size="mini"
+            @click="handleAdd"
+            v-hasPermi="['shop:tag:add']"
+          >新增</el-button>
+        </el-col>
+        <el-col :span="1.5">
+          <el-button
+            type="success"
+            plain
+            icon="el-icon-edit"
+            size="mini"
+            :disabled="single"
+            @click="handleUpdate"
+            v-hasPermi="['shop:tag:edit']"
+          >修改</el-button>
+        </el-col>
+        <el-col :span="1.5">
+          <el-button
+            type="danger"
+            plain
+            icon="el-icon-delete"
+            size="mini"
+            :disabled="multiple"
+            @click="handleDelete"
+            v-hasPermi="['shop:tag:remove']"
+          >删除</el-button>
+        </el-col>
+<!--        <el-col :span="1.5">-->
+<!--          <el-button-->
+<!--            type="warning"-->
+<!--            plain-->
+<!--            icon="el-icon-download"-->
+<!--            size="mini"-->
+<!--            :loading="exportLoading"-->
+<!--            @click="handleExport"-->
+<!--            v-hasPermi="['shop:tag:export']"-->
+<!--          >导出</el-button>-->
+<!--        </el-col>-->
+        <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table border v-loading="loading" :data="tagList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="ID" align="center" prop="id" />
+        <el-table-column label="企微公司" align="center" prop="corpId" />
+        <el-table-column label="视频ID" align="center" prop="videoId" />
+        <el-table-column label="看课中-标签组名" align="center" prop="watchingGroupName" />
+        <el-table-column label="看课中-标签名" align="center" prop="watchingTgName" />
+        <el-table-column label="完课-标签组名" align="center" prop="watchedGroupName" />
+        <el-table-column label="完课-标签名" align="center" prop="watchedTgName" />
+        <el-table-column label="操作" align="center" 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="['shop:tag:edit']"
+            >修改</el-button>
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-delete"
+              @click="handleDelete(scope.row)"
+              v-hasPermi="['shop:tag: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="600px" append-to-body>
+        <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+          <el-form-item label="企微公司" prop="corpId">
+            <el-select v-model="queryParams.corpId" placeholder="企微公司"  size="small" @change="updateCorpId()">
+              <el-option
+                v-for="dict in myQwCompanyList"
+                :key="dict.dictValue"
+                :label="dict.dictLabel"
+                :value="dict.dictValue"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="视频ID" prop="videoId">
+            <el-input v-model="form.videoId" placeholder="请输入视频ID" disabled/>
+          </el-form-item>
+          <el-form-item label="看课中-标签组" prop="watchingGroupTgId">
+            <el-select
+              v-model="form.watchingGroupTgId"
+              placeholder="请选择标签组"
+              @change="onWatchingGroupChange"
+              filterable
+            >
+              <el-option
+                v-for="group in tagGroups"
+                :key="group.groupId"
+                :label="group.name"
+                :value="group.groupId"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="看课中-标签" prop="watchingTagId">
+            <el-select
+              v-model="form.watchingTagId"
+              placeholder="请选择看课标签"
+              @change="onWatchingTagChange"
+              filterable>
+              <el-option
+                v-for="tag in watchingTagsInGroup"
+                :key="tag.tagId"
+                :label="tag.name"
+                :value="tag.tagId"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="完课-标签组" prop="watchedGroupTgId">
+            <el-select
+              v-model="form.watchedGroupTgId"
+              placeholder="请选择标签组"
+              @change="onWatchedGroupChange"
+              filterable>
+              <el-option
+                v-for="group in tagGroups"
+                :key="group.groupId"
+                :label="group.name"
+                :value="group.groupId"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="完课-标签" prop="watchedTagId">
+            <el-select
+              v-model="form.watchedTagId"
+              placeholder="请选择看课标签"
+              @change="onWatchedTagChange"
+              filterable>
+              <el-option
+                v-for="tag in watchedTagsInGroup"
+                :key="tag.tagId"
+                :label="tag.name"
+                :value="tag.tagId"
+              />
+            </el-select>
+          </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>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import {addTag, delTag, exportTag, getTag, listTag, updateTag} from "@/api/tag/api";
+import {getMyQwCompanyList} from "@/api/qw/user";
+import {listTagGroup} from "@/api/qw/tagGroup";
+
+export default {
+  name: 'BindLabelsDialog',
+  props: {
+    visible: {
+      type: Boolean,
+      required: true,
+    },
+    videoId: {
+      type: String,
+      required: true,
+    },
+  },
+  watch: {
+    visible(val) {
+      if (val) {
+        this.queryParams.videoId = this.videoId;
+        this.getList();
+      }
+    }
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 视频小节看课标签关联表格数据
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      myQwCompanyList:[],
+      // 看课中标签组
+      watchingTagsInGroup: [],
+      // 完课标签组
+      watchedTagsInGroup: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        corpId: null,
+        videoId: null,
+        watchingGroupId: null,
+        watchedGroupId: null,
+        watchingTgId: null,
+        watchedTgId: null,
+        watchingGroupTagId: null,
+        watchedGroupTagId: null,
+        watchingTagId: null,
+        watchedTagId: null,
+      },
+      tagList: [],
+      totalTags: 0,
+      pageNum: 1,
+      pageSize: 10,
+      loadingList: false,
+      selection: [],
+      formDialogVisible: false,
+      isEditing: false,
+      form: {
+        id: null,
+        corpId: null,
+        videoId: null,
+        watchingGroupId: null,
+        watchedGroupId: null,
+        watchingTgId:null,
+        watchedTgId: null,
+        watchingGroupTagId: null,
+        watchedGroupTagId: null,
+        watchingTagId: null,
+        watchedTagId:null
+      },
+      rules: {
+        corpId: [{ required: true, message: '请选择企微公司', trigger: 'change' }],
+        videoId: [{ required: true, message: '请输入视频ID', trigger: 'blur' }],
+        watchingGroupTgId: [{ required: true, message: '请选择看课中标签组', trigger: 'change' }],
+        watchingTagId: [{ required: true, message: '请选择看课中标签', trigger: 'change' }],
+        watchedGroupTgId: [{ required: true, message: '请选择完课标签组', trigger: 'change' }],
+        watchedTagId: [{ required: true, message: '请选择完课标签', trigger: 'change' }]
+      },
+      tagGroups: []
+    }
+  },
+  created() {
+    getMyQwCompanyList().then(response => {
+      this.myQwCompanyList = response.data;
+      if(this.myQwCompanyList != null && this.myQwCompanyList.length > 0){
+        this.queryParams.corpId = this.myQwCompanyList[0].dictValue;
+        this.getList();
+      }
+    });
+  },
+  methods: {
+    onWatchingTagChange(tagId){
+      let find = this.watchingTagsInGroup.find(e=> e.tagId === tagId);
+      this.form.watchingTgId = find.id;
+    },
+    onWatchedTagChange(tagId){
+      let find = this.watchedTagsInGroup.find(e=> e.tagId === tagId);
+      this.form.watchedTgId = find.id;
+    },
+    onWatchingGroupChange(groupId){
+      let find = this.tagGroups.find(e=> e.groupId===groupId);
+      this.watchingTagsInGroup=find.tag || [];
+      this.form.watchingGroupId = find.id;
+    },
+    onWatchedGroupChange(groupId){
+      let find = this.tagGroups.find(e=> e.groupId===groupId);
+      this.watchedTagsInGroup=find.tag || [];
+      this.form.watchedGroupId = find.id;
+    },
+    updateCorpId(){
+      this.getList();
+      // 查询所有的标签组
+      listTagGroup({
+        pageNum: 1,
+        pageSize: 1000,
+        corpId: this.queryParams.corpId
+      }).then((res) => {
+        if(res.code === 200) {
+          this.tagGroups = res.rows;
+        }
+      });
+    },
+    handleClose(){
+      this.$emit('update:visible', false);
+    },
+    /** 查询视频小节看课标签关联列表 */
+    getList() {
+      this.loading = true;
+      listTag(this.queryParams).then(response => {
+        this.tagList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+      this.$emit('close')
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        corpId: null,
+        videoId: null,
+        watchingGroupId: null,
+        watchedGroupId: null,
+        watchingTgId: null,
+        watchedTgId: null,
+        watchingGroupTagId: null,
+        watchedGroupTagId: null,
+        watchingTagId: null,
+        watchedTagId: null,
+        createTime: null,
+        updateTime: null,
+        createBy: null,
+        updateBy: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加视频小节看课标签关联";
+      this.form.videoId = this.videoId;
+      this.form.corpId = this.queryParams.corpId;
+
+      listTagGroup({
+        pageNum: 1,
+        pageSize: 1000,
+        corpId: this.queryParams.corpId
+      }).then((res) => {
+        if(res.code === 200) {
+          this.tagGroups = res.rows;
+        }
+      });
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids;
+
+      // 先加载标签组数据
+      listTagGroup({
+        pageNum: 1,
+        pageSize: 1000,
+        corpId: this.queryParams.corpId
+      }).then((res) => {
+        if(res.code === 200) {
+          this.tagGroups = res.rows;
+
+          // 再获取详情数据
+          getTag(id).then(response => {
+            this.form = response.data;
+
+            // 根据标签组ID加载对应的标签列表
+            if(this.form.watchingGroupTgId) {
+              let watchingGroup = this.tagGroups.find(e => e.id === this.form.watchingGroupId);
+              if(watchingGroup) {
+                this.watchingTagsInGroup = watchingGroup.tag || [];
+              }
+            }
+
+            if(this.form.watchedGroupTgId) {
+              let watchedGroup = this.tagGroups.find(e => e.id === this.form.watchedGroupId);
+              if(watchedGroup) {
+                this.watchedTagsInGroup = watchedGroup.tag || [];
+              }
+            }
+
+            this.open = true;
+            this.title = "修改视频小节看课标签关联";
+          });
+        }
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateTag(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addTag(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除视频小节看课标签关联编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delTag(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有视频小节看课标签关联数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportTag(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
+    }
+  }
+}
+</script>

+ 10 - 2
src/views/course/courseUserStatistics/qw/index.vue

@@ -11,7 +11,8 @@
         <treeselect
           style="width: 220px"
           :clearable="false"
-          v-model="queryParams.deptId"
+          v-model="queryParams.deptIds"
+          multiple
           :options="deptOptions"
           clearable
           :show-count="true"
@@ -453,6 +454,7 @@ export default {
   },
   methods: {
     getSummaries(param) {
+      let totalNum = 0;
       const { columns, data } = param;
       const sums = [];
       columns.forEach((column, index) => {
@@ -471,7 +473,13 @@ export default {
               return prev;
             }
           }, 0);
-
+          //第三列计数为进线数量和
+          if(index === 3){
+            totalNum = sums[index];
+          }
+          if(index > 3 && totalNum != 0){
+              sums[index] = sums[index] + "("+ ((sums[index] / totalNum) * 100).toFixed(2)  + "%)"
+          }
           if (
             column.property === "firstOnline" ||
             column.property === "firstOver" ||

+ 116 - 5
src/views/course/courseUserStatistics/qw/my.vue

@@ -11,7 +11,14 @@
         />
       </el-form-item>
       <el-form-item label="添加时间" prop="createTime">
-        <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="change"></el-date-picker>
+        <el-date-picker v-model="createTime" size="small"
+                        style="width: 220px"
+                        value-format="yyyy-MM-dd"
+                        type="daterange" range-separator="-"
+                        start-placeholder="开始日期"
+                        end-placeholder="结束日期"
+                        :picker-options="pickerOptions"
+                        @change="change"></el-date-picker>
       </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@@ -19,7 +26,14 @@
       </el-form-item>
     </el-form>
 
-    <el-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary height="600">
+    <el-table
+    border
+    v-loading="loading"
+    :data="courseWatchLogList"
+    @selection-change="handleSelectionChange"
+    :show-summary="true"
+    :summary-method="getSummaries"
+    ref="table1">
       <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="企微员工名称" align="center" prop="qwUserName" />
 
@@ -193,18 +207,103 @@ export default {
       rules: {
       },
       scheduleTime: null,
+      pickerOptions: {
+        disabledDate(time) {
+          // 获取6天前的日期(加上今天就是7天)
+          const sixDaysAgo = new Date();
+          sixDaysAgo.setDate(sixDaysAgo.getDate() - 6);
+          sixDaysAgo.setHours(0, 0, 0, 0);
+
+          // 获取明天的日期(不包括今天)
+          const tomorrow = new Date();
+          tomorrow.setDate(tomorrow.getDate() + 1);
+          tomorrow.setHours(0, 0, 0, 0);
+
+          return time.getTime() < sixDaysAgo.getTime() || time.getTime() >= tomorrow.getTime();
+
+        }
+      },
     };
   },
   created() {
     courseList().then(response => {
       this.courseLists = response.list;
     });
-    this.getList();
+
+    const today = new Date();
+    const sevenDaysAgo = new Date();
+    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 6);
+    this.queryParams.sTime = this.formatDate(sevenDaysAgo);
+    this.queryParams.eTime = this.formatDate(today);
+
     this.getDicts("sys_course_watch_log_type").then(response => {
       this.logTypeOptions = response.data;
     });
+
+    setTimeout(() => {
+      this.getList();
+    }, 200);
+
   },
   methods: {
+
+    formatDate(date) {
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    },
+
+     getSummaries(param) {
+      let totalNum = 0;
+      const { columns, data } = param;
+      const sums = [];
+      columns.forEach((column, index) => {
+        if (index === 0) {
+          sums[index] = "总计";
+          return;
+        }
+
+        const values = data.map((item) => Number(item[column.property]));
+        if (!values.every((value) => isNaN(value))) {
+          sums[index] = values.reduce((prev, curr) => {
+            const value = Number(curr);
+            if (!isNaN(value)) {
+              return prev + curr;
+            } else {
+              return prev;
+            }
+          }, 0);
+          //第三列计数为进线数量和
+          if(index === 3){
+            totalNum = sums[index];
+          }
+          if(index > 3 && totalNum != 0){
+              sums[index] = sums[index] + "("+ ((sums[index] / totalNum) * 100).toFixed(2)  + "%)"
+          }
+          if (
+            column.property === "firstOnline" ||
+            column.property === "firstOver" ||
+            column.property === "d1Online" ||
+            column.property === "d1Over" ||
+            column.property === "sign" ||
+            column.property === "interact" ||
+            column.property === "a" ||
+            column.property === "b" ||
+            column.property === "c" ||
+            column.property === "d" ||
+            column.property === "e" ||
+            column.property === "black" ||
+            column.property === "los" ||
+            column.property === "del"
+          ) {
+          }
+        } else {
+          sums[index] = "";
+        }
+      });
+      return sums;
+    },
     courseChange(row){
       this.queryParams.videoId=null;
       if(row === ''){
@@ -235,12 +334,19 @@ export default {
     },
     /** 查询短链课程看课记录列表 */
     getList() {
+
       this.loading = true;
       myQwWatchLogStatisticsList(this.queryParams).then(response => {
         this.courseWatchLogList = response.rows;
         this.total = response.total;
         this.loading = false;
       });
+       this.$nextTick(() => {
+        setTimeout(() => {
+          this.$refs.table1?.doLayout();
+        }, 2000);
+      });
+
     },
     // 取消按钮
     cancel() {
@@ -275,8 +381,13 @@ export default {
       this.resetForm("queryForm");
       this.createTime = null;
       this.scheduleTime = null;
-      this.queryParams.sTime = null;
-      this.queryParams.eTime = null;
+
+      const today = new Date();
+      const sevenDaysAgo = new Date();
+      sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 6);
+      this.queryParams.sTime = this.formatDate(sevenDaysAgo);
+      this.queryParams.eTime = this.formatDate(today);
+
       this.queryParams.scheduleStartTime = null;
       this.queryParams.scheduleEndTime = null;
       this.handleQuery();

+ 87 - 7
src/views/course/courseWatchLog/deptWatchLog.vue

@@ -383,11 +383,12 @@
         </el-table-column>
         <el-table-column label="课程名称" align="center" prop="courseName"/>
         <el-table-column label="小节名称" align="center" prop="videoName"/>
-        <el-table-column label="是否全部正确" align="center" prop="isRight">
-          <template slot-scope="scope">
-            <dict-tag :options="sysCompanyOr" :value="scope.row.isRight"></dict-tag>
-          </template>
-        </el-table-column>
+        <el-table-column label="是否全部正确" align="center" prop="isRightText"/>
+<!--        <el-table-column label="是否全部正确" align="center" prop="isRight">-->
+<!--          <template slot-scope="scope">-->
+<!--            <dict-tag :options="sysCompanyOr" :value="scope.row.isRight"></dict-tag>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
         <el-table-column label="销售名称" align="center" prop="companyUserName"/>
         <el-table-column label="企微员工名称" align="center" prop="qwUserName"/>
         <el-table-column label="公司名称" align="center" prop="companyName"/>
@@ -627,13 +628,30 @@ export default {
     courseList().then(response => {
       this.courseLists = response.list;
     });
-    this.getList();
+
     this.getDicts("sys_course_watch_log_type").then(response => {
       this.logTypeOptions = response.data;
     });
+
+    // 设置默认当天时间 xgb 防止频繁查询大量数据
+    this.setToday();
+
+    this.getList();
     this.getCompanyUserListLikeName(true);
+    this.loading=false;
   },
   methods: {
+    setToday(){
+      const today = new Date();
+      const todayStart = new Date(today);
+      todayStart.setHours(0, 0, 0, 0);
+      const todayEnd = new Date(today);
+      todayEnd.setHours(23, 59, 59, 999);
+
+      this.createTimeText = [this.formatDate(todayStart), this.formatDate(todayEnd)];
+      this.queryParams.sTime = this.formatDate(todayStart);
+      this.queryParams.eTime = this.formatDate(todayEnd);
+    },
     handleSendTypeChange() {
       this.handleQuery(); // 重新查询列表
     },
@@ -685,6 +703,14 @@ export default {
     },
     /** 查询短链课程看课记录列表 */
     getList() {
+      // xgb 看课数据量太大必须限制时间if (this.isEmptyArray(this.createTimeText) &&
+      if (this.isEmptyArray(this.createTimeText) &&
+        this.isEmptyArray(this.updateTimeText) &&
+        this.isEmptyArray(this.scheduleTimeText)) {
+        this.$message.warning('请选择创建时间或营期时间或最新更新时间');
+        return;
+      }
+
       this.loading = true;
       if(this.queryParams.logType == "10"){
         this.queryParams.logType = null;
@@ -693,7 +719,14 @@ export default {
         this.courseWatchLogList = response.rows;
         this.total = response.total;
         this.loading = false;
-      });
+      }).catch(() => {
+          this.loading = false;
+        }
+      );
+    },
+    // 添加辅助方法
+    isEmptyArray(arr) {
+      return !arr || arr.length === 0;
     },
     // 取消按钮
     cancel() {
@@ -753,6 +786,8 @@ export default {
       // 统一重置日历组件
       this.resetCalendars();
 
+      this.setToday();
+
       this.handleQuery();
     },
     // 多选框选中数据
@@ -813,6 +848,14 @@ export default {
     },
     /** 导出按钮操作 */
     handleExport() {
+      // xgb 看课数据量太大必须限制时间
+      if (this.isEmptyArray(this.createTimeText) &&
+        this.isEmptyArray(this.updateTimeText) &&
+        this.isEmptyArray(this.scheduleTimeText)) {
+        this.$message.warning('请选择创建时间或营期时间或最新更新时间');
+        return;
+      }
+
       const that = this
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
@@ -876,6 +919,12 @@ export default {
     // 营期时间
     handleScheduleTimeChange(scheduleTime) {
       if (scheduleTime && scheduleTime.length >= 2) {
+        if(!this.checkDateRangeLimit(scheduleTime)){
+          this.scheduleTimeText = null;
+          this.queryParams.scheduleStartTime=null;
+          this.queryParams.scheduleStartTime=null;
+          return;
+        }
         // this.scheduleTimeText = this.formatDateRange(scheduleTime);
         this.queryParams.scheduleStartTime = scheduleTime[0] || null;
         this.queryParams.scheduleEndTime = scheduleTime[1] || null;
@@ -885,9 +934,34 @@ export default {
         this.queryParams.scheduleEndTime = null;
       }
     },
+    checkDateRangeLimit(dateRange) {
+      if (dateRange && dateRange.length >= 2) {
+        const startDate = new Date(dateRange[0]);
+        const endDate = new Date(dateRange[1]);
+
+        // 设置时间为当天开始,避免时间部分影响计算
+        startDate.setHours(0, 0, 0, 0);
+        endDate.setHours(0, 0, 0, 0);
+
+        const timeDiff = Math.abs(endDate - startDate);
+        const diffDays = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
+
+        if (diffDays > 31) { // maxDays-1 因为包含起始日
+          this.$message.warning('时间区间不能超过一个月');
+          return false;
+        }
+      }
+      return true;
+    },
     // 创建时间
     createChange(createTime) {
       if (createTime && createTime.length >= 2) {
+        if(!this.checkDateRangeLimit(createTime)){
+          this.createTimeText = null;
+          this.queryParams.sTime=null;
+          this.queryParams.eTime=null;
+          return;
+        }
         // this.createTimeText = this.formatDateRange(createTime);
         this.queryParams.sTime = this.formatDate(createTime[0]) || null;
         this.queryParams.eTime = this.formatDate(createTime[1]) || null;
@@ -901,6 +975,12 @@ export default {
     // 更新时间
     updateChange(updateTime) {
       if (updateTime && updateTime.length >= 2) {
+        if(!this.checkDateRangeLimit(updateTime)){
+          this.updateTimeText = null;
+          this.queryParams.upSTime=null;
+          this.queryParams.upETime=null;
+          return;
+        }
         this.updateTimeText = this.formatDateRange(updateTime);
         this.queryParams.upSTime = updateTime[0] || null;
         this.queryParams.upETime = updateTime[1] || null;

+ 81 - 1
src/views/course/courseWatchLog/index.vue

@@ -815,16 +815,38 @@ export default {
     courseList().then(response => {
       this.courseLists = response.list;
     });
-    this.getList();
+
     this.getDicts("sys_course_watch_log_type_new").then(response => {
       this.logTypeOptions = response.data;
     });
     this.getDicts("sys_course_project").then(response => {
       this.projectOptions = response.data;
     });
+
+    // 设置默认当天时间 xgb 防止频繁查询大量数据
+    this.setToday();
+
+    this.getList();
     this.getCompanyUserListLikeName(true);
+
+    this.loading=false;
   },
   methods: {
+    // 添加辅助方法
+    isEmptyArray(arr) {
+      return !arr || arr.length === 0;
+    },
+    setToday(){
+      const today = new Date();
+      const todayStart = new Date(today - 60*60*24*7*1000) ;
+      todayStart.setHours(0, 0, 0, 0);
+      const todayEnd = new Date(today);
+      todayEnd.setHours(23, 59, 59, 999);
+
+      this.createTimeText = [this.formatDate(todayStart), this.formatDate(todayEnd)];
+      this.queryParams.sTime = this.formatDate(todayStart);
+      this.queryParams.eTime = this.formatDate(todayEnd);
+    },
     /**
      * 处理所属销售下拉框显示状态变化
      */
@@ -1001,10 +1023,35 @@ export default {
         this.videoList=response.list
       });
     },
+    checkDateRangeLimit(dateRange) {
+      if (dateRange && dateRange.length >= 2) {
+        const startDate = new Date(dateRange[0]);
+        const endDate = new Date(dateRange[1]);
 
+        // 设置时间为当天开始,避免时间部分影响计算
+        startDate.setHours(0, 0, 0, 0);
+        endDate.setHours(0, 0, 0, 0);
+
+        const timeDiff = Math.abs(endDate - startDate);
+        const diffDays = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
+
+        if (diffDays > 31) { // maxDays-1 因为包含起始日
+          this.$message.warning('时间区间不能超过一个月');
+          return false;
+        }
+      }
+      return true;
+    },
     // 营期时间
     handleScheduleTimeChange(scheduleTime) {
       if (scheduleTime && scheduleTime.length >= 2) {
+        if(!this.checkDateRangeLimit(scheduleTime)){
+          this.scheduleTimeText = null;
+          this.queryParams.scheduleStartTime=null;
+          this.queryParams.scheduleStartTime=null;
+          return;
+        }
+
         // this.scheduleTimeText = this.formatDateRange(scheduleTime);
         this.queryParams.scheduleStartTime = scheduleTime[0] || null;
         this.queryParams.scheduleEndTime = scheduleTime[1] || null;
@@ -1019,6 +1066,13 @@ export default {
     // 创建时间
     createChange(createTime) {
       if (createTime && createTime.length >= 2) {
+        if(!this.checkDateRangeLimit(createTime)){
+          this.createTimeText = null;
+          this.queryParams.sTime=null;
+          this.queryParams.eTime=null;
+          return;
+        }
+
         // this.createTimeText = this.formatDateRange(createTime);
         this.queryParams.sTime = this.formatDate(createTime[0]) || null;
         this.queryParams.eTime = this.formatDate(createTime[1]) || null;
@@ -1032,6 +1086,12 @@ export default {
     // 更新时间
     updateChange(updateTime) {
       if (updateTime && updateTime.length >= 2) {
+        if(!this.checkDateRangeLimit(updateTime)){
+          this.updateTimeText = null;
+          this.queryParams.upSTime=null;
+          this.queryParams.upETime=null;
+          return;
+        }
         // this.updateTimeText = this.formatDateRange(updateTime);
         this.queryParams.upSTime = updateTime[0] || null;
         this.queryParams.upETime = updateTime[1] || null;
@@ -1156,6 +1216,14 @@ export default {
     },
     /** 查询短链课程看课记录列表 */
     getList() {
+      // xgb 看课数据量太大必须限制时间
+      if (this.isEmptyArray(this.createTimeText) &&
+        this.isEmptyArray(this.updateTimeText) &&
+        this.isEmptyArray(this.scheduleTimeText)) {
+        this.$message.warning('请选择创建时间或营期时间或最新更新时间');
+        return;
+      }
+
       this.loading = true;
       if(this.queryParams.logType == "10"){
         this.queryParams.logType = null;
@@ -1165,6 +1233,8 @@ export default {
         this.courseWatchLogList = response.rows;
         this.total = response.total;
         this.loading = false;
+      }).catch(() => {
+        this.loading = false;
       });
     },
     // 取消按钮
@@ -1223,12 +1293,15 @@ export default {
       this.queryParams.scheduleEndTime = null;
       this.queryParams.sopId = null; // 重置SOP ID
 
+
       // 重置SOP搜索
       this.handleSopClear();
 
       // 统一重置日历组件
       this.resetCalendars();
 
+      this.setToday();
+
       this.handleQuery();
     },
     // 多选框选中数据
@@ -1289,6 +1362,13 @@ export default {
     },
     /** 导出按钮操作 */
     handleExport() {
+      // xgb 看课数据量太大必须限制时间
+      if (this.isEmptyArray(this.createTimeText) &&
+        this.isEmptyArray(this.updateTimeText) &&
+        this.isEmptyArray(this.scheduleTimeText)) {
+        this.$message.warning('请选择创建时间或营期时间或最新更新时间');
+        return;
+      }
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
           confirmButtonText: "确定",

+ 61 - 1
src/views/course/courseWatchLog/qw/statistics.vue

@@ -54,7 +54,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary>
+    <el-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary :summary-method="getSummaries" >
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="企微员工名称" align="center" prop="qwUserName" />
       <el-table-column label="发课时间" align="center" prop="createTime"/>
@@ -159,6 +159,66 @@ export default {
     });
   },
   methods: {
+     getSummaries(param) {
+      let totalNum = 0;
+      const { columns, data } = param;
+      const sums = [];
+      let totalOnline = 0;
+      let totalFinishCourse = 0;
+      columns.forEach((column, index) => {
+        if (index === 0) {
+          sums[index] = "总计";
+          return;
+        }
+        const values = data.map((item) => Number(item[column.property]));
+        if (!values.every((value) => isNaN(value))) {
+          sums[index] = values.reduce((prev, curr) => {
+            const value = Number(curr);
+            if (!isNaN(value)) {
+              return prev + curr;
+            } else {
+              return prev;
+            }
+          }, 0);
+          if(index > 3 && totalNum != 0){
+              sums[index] = sums[index] + "("+ ((sums[index] / totalNum) * 100).toFixed(2)  + "%)"
+          }
+          if( column.property=="videoName"){
+            sums[index] = "";
+          }
+          if (
+            column.property === "type3" ||
+            column.property === "type1" ||
+            column.property === "type2" ||
+            column.property === "type4" ||
+            column.property === "sendNumber" ||
+            column.property === "isUserWaitNumber" ||
+            column.property === "noUserWaitNumber" ||
+            column.property === "onLineRate" ||
+            column.property === "finishedRate" ||
+            column.property === "redAmount" 
+          ) {
+          }
+        } else {
+          sums[index] = "";
+          if(index === 12 || index === 13){
+            //  let numbers = data.map(item => {
+            //   return parseFloat(item[column.property].replace('%', '')) || 0; // 处理空值或无效值
+            // });
+            // let sumRates = numbers.reduce((acc, curr) => acc + curr, 0);
+            // console.log("index:" + index + " rates:" + sumRates + " data.length:" + data.length);
+            if(index === 12 && !!sums[9]){
+              let sumsNum = sums[6] + sums[7] + sums[8];
+              sums[index] = (sumsNum * 100 / sums[9]).toFixed(2) + '%';
+            } else if(index === 13 && !!sums[9]){
+              sums[index] =  (sums[7] * 100 / sums[9]).toFixed(2) + '%';
+            }
+          } 
+        }
+      });
+      console.log(sums);
+      return sums;
+    },
     courseChange(row){
       this.queryParams.videoId=null;
       if(row === ''){

+ 230 - 14
src/views/course/courseWatchLog/watchLog.vue

@@ -291,6 +291,28 @@
           >批量移除标签</el-button>
         </el-col>
       </el-col>
+      <el-col :span="1.5" v-if="queryParams.sendType == 2">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          @click="handleBatchUpdateNotes"
+          v-hasPermi="['qw:externalContact:edit']"
+        >批量修改备注
+        </el-button>
+      </el-col>
+      <el-col :span="1.5" v-if="queryParams.sendType == 2">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          @click="handleBatchUpdateNotesFilter"
+          v-hasPermi="['qw:externalContact:edit']"
+        >批量修改备注(筛选条件)
+        </el-button>
+      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
@@ -518,7 +540,7 @@
                 @click="tagSelection(tagItem)"
                 :class="{ 'tag-selected': tagItem.isSelected }"
               >
-                {{ tagItem.name }}
+                {{ tagItem.name }} 【{{tagItem.corpName}}】
               </a>
             </div>
           </div>
@@ -587,7 +609,53 @@
         <el-button @click="resultDialogVisible = false">关闭</el-button>
       </span>
     </el-dialog>
-
+     <el-dialog title="批量添加客户备注" :visible.sync="notesOpen.open" width="800px" append-to-body>
+      <el-card>
+        <el-row>
+          <el-col>
+            <el-radio-group v-model="notesOpen.nameType" style="margin-bottom: 2%">
+              <el-radio :label="1">
+                客户名称添加在【新备注】【前】
+              </el-radio>
+              <el-radio :label="2">
+                客户名称添加在【新备注】【后】
+              </el-radio>
+              <el-radio :label="3">
+                不添加客户名称
+              </el-radio>
+            </el-radio-group>
+          </el-col>
+          <el-col>
+            <el-radio-group v-model="notesOpen.type">
+              <el-radio
+                :label="1"
+              >添加【新备注】在最【前】面
+              </el-radio>
+              <el-radio
+                :label="2"
+              >添加【新备注】在最【后】面
+              </el-radio>
+              <el-radio
+                :label="3"
+              >替换所有备注
+              </el-radio>
+            </el-radio-group>
+          </el-col>
+          <el-col>
+            <el-input v-model="notesOpen.notes" placeholder="请输入客户备注(最多20个字,含已有的)" clearable size="small"
+                      maxlength="20" show-word-limit style="width: 500px;margin-top: 3%"/>
+            <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+              <i class="el-icon-info"></i>
+              由于企业微信官方限制,备注最多20个字,且自动会去除末尾超出的字
+            </div>
+          </el-col>
+        </el-row>
+      </el-card>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="notesSubmitForm()">确 定</el-button>
+        <el-button @click="notesCancel()">取消</el-button>
+      </div>
+    </el-dialog>
 
   </div>
 </template>
@@ -605,7 +673,7 @@ import {courseList, myListCourseRedPacketLog, videoList} from '@/api/course/cour
 import {myListLogs} from "@/api/course/courseAnswerlogs";
 import {getMyQwUserList} from "@/api/qw/user";
 import {searchTags} from "../../../api/qw/tag";
-import {addTagByWatch, delTagByWatch} from "../../../api/qw/externalContact";
+import {addTagByWatch, delTagByWatch,batchUpdateExternalContactNotes,batchUpdateExternalContactNotesByWatchLog} from "../../../api/qw/externalContact";
 import {allListTagGroup} from "../../../api/qw/tagGroup";
 import Vue from 'vue'
 import Calendar from 'vue-mobile-calendar'
@@ -781,14 +849,22 @@ export default {
       // 表单参数
       form: {},
       // 表单校验
-      rules: {}
+      rules: {},
+      notesOpen: {
+        type: 1,
+        nameType: 3,
+        addType: 0,
+        filter: false,
+        open: false,
+        notes: null,
+      },
     };
   },
   created() {
     courseList().then(response => {
       this.courseLists = response.list;
     });
-    this.getList();
+
     this.getDicts("sys_course_watch_log_type_new").then(response => {
       this.logTypeOptions = response.data;
     });
@@ -796,14 +872,30 @@ export default {
     this.getDicts("sys_company_or").then(response => {
       this.sysCompanyOr = response.data;
     });
-    getMyQwUserList().then(response => {
-      this.myQwUserList = response.data;
-    });
     this.getDicts("sys_course_project").then(response => {
       this.projectOptions = response.data;
     });
+    // 设置默认当天时间 xgb 防止频繁查询大量数据
+    this.setToday();
+
+    this.getList();
+    getMyQwUserList().then(response => {
+      this.myQwUserList = response.data;
+    });
+
   },
   methods: {
+    setToday(){
+      const today = new Date();
+      const todayStart = new Date(today - 60*60*24*7*1000) ;
+      todayStart.setHours(0, 0, 0, 0);
+      const todayEnd = new Date(today);
+      todayEnd.setHours(23, 59, 59, 999);
+
+      this.createTimeText = [this.formatDate(todayStart), this.formatDate(todayEnd)];
+      this.queryParams.sTime = this.formatDate(todayStart);
+      this.queryParams.eTime = this.formatDate(todayEnd);
+    },
 
     handleSendTypeChange() {
       // 重置相关参数
@@ -838,10 +930,10 @@ export default {
       this.qecCreateTime = [];
       this.periodTime = [];
 
-      this.scheduleTimeText = '';
-      this.createTimeText = '';
-      this.updateTimeText = '';
-      this.qecCreateTimeText = '';
+      this.scheduleTimeText = [];
+      this.createTimeText = [];
+      this.updateTimeText = [];
+      this.qecCreateTimeText = [];
       this.periodTimeText = [];
 
       // 强制刷新日历组件
@@ -888,20 +980,51 @@ export default {
     // 营期时间
     handleScheduleTimeChange(scheduleTime) {
       if (scheduleTime && scheduleTime.length >= 2) {
+        if(!this.checkDateRangeLimit(scheduleTime)){
+          this.scheduleTimeText = null;
+          this.queryParams.scheduleStartTime=null;
+          this.queryParams.scheduleStartTime=null;
+          return;
+        }
+
         // this.scheduleTimeText = this.formatDateRange(scheduleTime);
         this.queryParams.scheduleStartTime = scheduleTime[0] || null;
         this.queryParams.scheduleEndTime = scheduleTime[1] || null;
-        console.log(this.queryParams.scheduleStartTime)
-        console.log(this.queryParams.scheduleEndTime)
       } else {
         this.scheduleTimeText = [];
         this.queryParams.scheduleStartTime = null;
         this.queryParams.scheduleEndTime = null;
       }
     },
+    checkDateRangeLimit(dateRange) {
+      if (dateRange && dateRange.length >= 2) {
+        const startDate = new Date(dateRange[0]);
+        const endDate = new Date(dateRange[1]);
+
+        // 设置时间为当天开始,避免时间部分影响计算
+        startDate.setHours(0, 0, 0, 0);
+        endDate.setHours(0, 0, 0, 0);
+
+        const timeDiff = Math.abs(endDate - startDate);
+        const diffDays = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
+
+        if (diffDays > 31) { // maxDays-1 因为包含起始日
+          this.$message.warning('时间区间不能超过一个月');
+          return false;
+        }
+      }
+      return true;
+    },
     // 创建时间
     createChange(createTime) {
       if (createTime && createTime.length >= 2) {
+        if(!this.checkDateRangeLimit(createTime)){
+          this.createTimeText = null;
+          this.queryParams.sTime=null;
+          this.queryParams.eTime=null;
+          return;
+        }
+
         // this.createTimeText = this.formatDateRange(createTime);
         this.queryParams.sTime = this.formatDate(createTime[0]) || null;
         this.queryParams.eTime = this.formatDate(createTime[1]) || null;
@@ -915,6 +1038,13 @@ export default {
     // 更新时间
     updateChange(updateTime) {
       if (updateTime && updateTime.length >= 2) {
+        if(!this.checkDateRangeLimit(updateTime)){
+          this.updateTimeText = null;
+          this.queryParams.upSTime=null;
+          this.queryParams.upETime=null;
+          return;
+        }
+
         // this.updateTimeText = this.formatDateRange(updateTime);
         this.queryParams.upSTime = updateTime[0] || null;
         this.queryParams.upETime = updateTime[1] || null;
@@ -1027,6 +1157,14 @@ export default {
 
     /** 查询短链课程看课记录列表 */
     getList() {
+      // xgb 看课数据量太大必须限制时间if (this.isEmptyArray(this.createTimeText) &&
+      if (this.isEmptyArray(this.createTimeText) &&
+        this.isEmptyArray(this.updateTimeText) &&
+        this.isEmptyArray(this.scheduleTimeText)) {
+        this.$message.warning('请选择创建时间或营期时间或最新更新时间');
+        return;
+      }
+
       this.loading = true;
       let param = JSON.parse(JSON.stringify(this.queryParams));
       if (param.logType == "10") {
@@ -1036,6 +1174,8 @@ export default {
         this.courseWatchLogList = response.rows;
         this.total = response.total;
         this.loading = false;
+      }).catch(() => {
+        this.loading = false;
       });
     },
     // 取消按钮
@@ -1100,6 +1240,8 @@ export default {
       // 统一重置日历组件
       this.resetCalendars();
 
+      this.setToday();
+
       this.handleQuery();
     },
     // 多选框选中数据
@@ -1159,8 +1301,19 @@ export default {
       }).catch(() => {
       });
     },
+    // 添加辅助方法
+    isEmptyArray(arr) {
+      return !arr || arr.length === 0;
+    },
     /** 导出按钮操作 */
     handleExport() {
+      // xgb 看课数据量太大必须限制时间
+      if (this.isEmptyArray(this.createTimeText) &&
+        this.isEmptyArray(this.updateTimeText) &&
+        this.isEmptyArray(this.scheduleTimeText)) {
+        this.$message.warning('请选择创建时间或营期时间或最新更新时间');
+        return;
+      }
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
         confirmButtonText: "确定",
@@ -1445,6 +1598,69 @@ export default {
       this.queryParams.sopId = null;
       this.sopSearchText = '';
     },
+    handleBatchUpdateNotesFilter() {
+      this.notesOpen.open = true;
+      this.notesOpen.filter = true;
+    },
+    handleBatchUpdateNotes() {
+
+      if (this.ids == null || this.ids == "") {
+        return this.$message('请选择需要添加备注的客户');
+      }
+
+      this.notesOpen.open = true;
+      this.notesOpen.filter = false;
+
+    },
+    notesCancel(){
+      this.notesOpen={
+        open: false,
+        notes: null,
+        type: 1,
+        nameType:3,
+      }
+    },
+    notesSubmitForm() {
+
+      if (this.notesOpen.notes == null || this.notesOpen.notes == "") {
+        return this.$message.error("请输入备注内容");
+      }
+
+      // let loadingRock = this.$loading({
+      //   lock: true,
+      //   text: '正在执行中请稍后~~请不要刷新页面!!',
+      //   spinner: 'el-icon-loading',
+      //   background: 'rgba(0, 0, 0, 0.7)'
+      // });
+
+      let obj = JSON.parse(JSON.stringify(this.queryParams))
+      console.log(obj);
+      if(obj.tagIds !== null && obj.tagIds !== undefined && obj.tagIds !== ''){
+        obj.tagIds = obj.tagIds.split(",");
+      }
+      batchUpdateExternalContactNotesByWatchLog({
+        // addType: 0,
+        watchLogIds: this.ids,
+        notes: this.notesOpen.notes,
+        type: this.notesOpen.type,
+        nameType: this.notesOpen.nameType,
+        filter: this.notesOpen.filter,
+        watchLogParam: obj,
+        fromMyList:1
+      }).then(res => {
+
+        this.resultMessage = res.msg;
+        this.$message.success("正在执行中...");
+        // this.resultDialogVisible = true; // 显示弹窗
+        // this.resultTitle = '批量修改备注结果';
+
+      }).finally(res => {
+        this.getList();
+        // loadingRock.close();
+        this.notesCancel();
+      })
+
+    },
   }
 };
 </script>

+ 0 - 7
src/views/crm/customer/full.vue

@@ -487,13 +487,6 @@ export default {
           this.citys=res.data;
         })
     },
-    handleShow(row){
-      var that=this;
-      that.show.open=true;
-      setTimeout(() => {
-          that.$refs.customerDetails.getDetails(row.customerId);
-      }, 200);
-    },
     /** 查询客户列表 */
     getList() {
       this.loading = true;

+ 43 - 4
src/views/index.vue

@@ -3,7 +3,7 @@
     <!-- 数据概览 (Data Overview) -->
     <el-card class="overview-section" shadow="never">
       <el-row :gutter="20">
-        <el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16" class="companybox">
+        <el-col :xs="24" :sm="24" :md="16" :lg="12" :xl="12" class="companybox">
           <img src="../assets/images/topbg.png" alt="" class="topimg">
           <img src="../assets/images/topbg.png" alt="" class="bottomimg">
           <div class="companyboxtitle">
@@ -54,8 +54,7 @@
           </div>
         </el-col>
 
-
-        <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8" class="propertyboxtitle">
+        <el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" class="propertyboxtitle">
           <div class="property_title">
             资产概览
           </div>
@@ -83,6 +82,33 @@
           </div>
 
         </el-col>
+
+
+        <el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" class="propertyboxtitle">
+          <div class="property_title">
+            红包概览
+          </div>
+          <div class="propertyboxflex">
+            <div class="property-card propertyline">
+              <div class="property-title">
+                <i class="el-icon-money"></i>
+                红包余额 (元)
+              </div>
+              <div class="card-value highlight">
+                <count-to :decimals="2"  :start-val="0" :end-val="redBalance" :duration="3600" class="card-panel-num" />
+              </div>
+            </div>
+            <div class="property-card">
+              <div class="property-title">
+                <span>今日余额变化 (元)</span>
+              </div>
+              <div class="card-value highlight">
+                <count-to :decimals="2" :start-val="0" :end-val="redTodayComsumption" :duration="3600" class="card-panel-num" />
+              </div>
+            </div>
+          </div>
+
+        </el-col>
       </el-row>
 
       <le-row :gutter="20">
@@ -528,8 +554,9 @@ import {
   authorizationInfo,
   dealerAggregated, deaMemberTopTen, rechargeComsumption, rewardMoneyTopTen, rewardMoneyTrend,
   smsBalance, thisMonthOrderCount, thisMonthRecvCount, trafficLog,
-  watchCourseTopTen, watchEndPlayTrend,getWatchCourseStatisticsData
+  watchCourseTopTen, watchEndPlayTrend,getWatchCourseStatisticsData,
 } from "@/api/statistics/statistics";
+import {redPacketInfo} from "@/api/company/redPacket";
 import dayjs from 'dayjs';
 
 
@@ -975,6 +1002,8 @@ export default {
       todayComsumption: 0,
       yesterdayComsumption: 0,
       balance: 0,
+      redBalance:0,
+      redTodayComsumption:0,
       autoRefreshInterval: null,
       // 今日新增用户数
       todayIncreaseUserNum: 0,
@@ -1140,6 +1169,16 @@ export default {
         }
       });
 
+      // 获取红包概览
+      redPacketInfo().then(res=>{
+        if(res.code === 200){
+          if(res.data){
+            this.redBalance = res.data.redBalance;
+            this.redTodayComsumption = res.data.redTodayComsumption;
+          }
+        }
+      })
+
       trafficLog().then(res=>{
         if(res.code === 200) {
           this.todayTraffic = res.data.today;

+ 1 - 1
src/views/qw/contactWay/group.vue

@@ -16,7 +16,7 @@
     <el-table v-loading="loading" :data="materialGroupList"
               @row-click="changeCurrentRow"
               :row-style="rowStyle"
-              :cell-style="setSellStyle" >
+              :cell-style="setSellStyle" height="600px">
       <!-- 保留的列 -->
       <el-table-column label="组名" align="center" prop="groupName" >
         <template slot-scope="scope">

+ 141 - 19
src/views/qw/externalContact/deptIndex.vue

@@ -166,6 +166,23 @@
 
 
       </el-form-item>
+      <el-form-item label="排除标签" prop="outTagIds">
+        <div @click="hangleChangeOutTags()"
+             style="cursor: pointer; border: 1px solid #e6e6e6; background-color: white; overflow: hidden; flex-grow: 1;width: 250px">
+          <div style="min-height: 35px; max-height: 200px; overflow-y: auto;">
+            <el-tag type="success"
+                    closable
+                    :disable-transitions="false"
+                    v-for="list in this.outSelectTags"
+                    :key="list.tagId"
+                    @close="handleCloseOutTags(list)"
+                    style="margin: 3px;"
+            >{{ list.name }}
+            </el-tag>
+          </div>
+        </div>
+      </el-form-item>
+
       <el-form-item label="备注" prop="remark">
         <el-input
           v-model="queryParams.remark"
@@ -535,8 +552,8 @@
         @pagination="getPageListTagGroup"
       />
       <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="tagSubmitForm()">确 定</el-button>
-        <el-button @click="tagCancel()">取消</el-button>
+        <el-button type="primary" @click="tagSubmitForm(changeTagDialog.type)">确 定</el-button>
+        <el-button @click="tagCancel(changeTagDialog.type)">取消</el-button>
       </div>
     </el-dialog>
 
@@ -970,6 +987,7 @@ export default {
       changeTagDialog:{
         title:"",
         open:false,
+        type: null,
       },
 
       queryTagParams:{
@@ -996,6 +1014,7 @@ export default {
         gender: null,
         description: null,
         tagIds: null,
+        outTagIds: null,
         remark:null,
         remarkMobiles: null,
         remarkCorpName: null,
@@ -1015,6 +1034,7 @@ export default {
         levelType:null
       },
       selectTags:[],
+      outSelectTags:[],
       // 表单参数
       form: {},
       tagList:[],
@@ -1335,6 +1355,7 @@ export default {
 
       this.changeTagDialog.title="搜索的标签"
       this.changeTagDialog.open=true;
+      this.changeTagDialog.type = 1;
 
       // 获取 tagListFormIndex 中的所有 tagId,用于快速查找
       const selectedTagIds = new Set(
@@ -1356,6 +1377,33 @@ export default {
 
     },
 
+
+    //选择排除标签
+    hangleChangeOutTags() {
+      this.changeTagDialog.title="搜索的标签"
+      this.changeTagDialog.open=true;
+      this.changeTagDialog.type = 2;
+
+      // 获取 tagListFormIndex 中的所有 tagId,用于快速查找
+      const selectedTagIds = new Set(
+        (this.outSelectTags || []).map(tagItem => tagItem?.tagId)
+      );
+
+      this.queryTagParams.name=null;
+
+      this.getPageListTagGroup();
+
+      setTimeout(() => {
+        for (let i = 0; i < this.tagGroupList.length; i++) {
+          for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+            this.tagGroupList[i].tag[x].isSelected = selectedTagIds.has(this.tagGroupList[i].tag[x].tagId);
+          }
+        }
+      }, 200);
+
+    },
+
+
     //删除一些选择的标签
     handleCloseTags(list){
       const ls = this.selectTags.findIndex(t => t.tagId === list.tagId);
@@ -1385,6 +1433,36 @@ export default {
 
     },
 
+
+    //删除一些排除的标签
+    handleCloseOutTags(list){
+      const ls = this.outSelectTags.findIndex(t => t.tagId === list.tagId);
+      if (ls !== -1) {
+        this.outSelectTags.splice(ls, 1);
+        this.outSelectTags = [...this.selectTags];
+      }
+
+      if (this.outSelectTags!=null && this.outSelectTags.length>0){
+        // 确保 this.form.tags 是数组
+        if (!this.queryParams.outTagIds) {
+          this.queryParams.outTagIds = []; // 如果未定义,初始化
+        } else {
+          this.queryParams.outTagIds = []; // 清空已有数据
+        }
+
+        // 遍历并添加 tagId
+        this.outSelectTags.forEach(tag => {
+          if (tag.tagId) { // 确保 tagId 存在
+            this.queryParams.outTagIds.push(tag.tagId);
+          }
+        });
+        this.queryParams.outTagIds=this.queryParams.outTagIds.join(",");
+      }else {
+        this.queryParams.outTagIds=null;
+      }
+
+    },
+
     //重新获取页面数据
     refreshList(){
       this.getList();
@@ -1588,6 +1666,24 @@ export default {
         this.queryParams.tagIds=null;
       }
 
+      if (this.outSelectTags!=null && this.outSelectTags.length>0){
+        // 确保 this.form.tags 是数组
+        if (!this.queryParams.outTagIds) {
+          this.queryParams.outTagIds = []; // 如果未定义,初始化
+        } else {
+          this.queryParams.outTagIds = []; // 清空已有数据
+        }
+
+        // 遍历并添加 tagId
+        this.outSelectTags.forEach(tag => {
+          if (tag.tagId) { // 确保 tagId 存在
+            this.queryParams.outTagIds.push(tag.tagId);
+          }
+        });
+        this.queryParams.outTagIds=this.queryParams.outTagIds.join(",");
+      }else {
+        this.queryParams.outTagIds=null;
+      }
 
       this.queryParams.pageNum = 1;
       this.getList();
@@ -1645,32 +1741,57 @@ export default {
     },
 
     //确定选择标签
-    tagSubmitForm(){
+    tagSubmitForm(type){
 
-      for (let i = 0; i < this.tagGroupList.length; i++) {
-        for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
-          if (this.tagGroupList[i].tag[x].isSelected === true) {
+      if (type==1) {
+        for (let i = 0; i < this.tagGroupList.length; i++) {
+          for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+            if (this.tagGroupList[i].tag[x].isSelected === true) {
+
+              if (!this.selectTags) {
+                this.selectTags = [];
+              }
+
+              // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+              let tagExists = this.selectTags.some(
+                tag => tag.id === this.tagGroupList[i].tag[x].id
+              );
 
-            if (!this.selectTags) {
-              this.selectTags = [];
+              // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+              if (!tagExists) {
+                this.selectTags.push(this.tagGroupList[i].tag[x]);
+              }
             }
+          }
+        }
+        if (!this.selectTags || this.selectTags.length === 0) {
+          return this.$message('请选择标签');
+        }
+      }else if (type == 2) {
+        for (let i = 0; i < this.tagGroupList.length; i++) {
+          for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+            if (this.tagGroupList[i].tag[x].isSelected === true) {
 
-            // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
-            let tagExists = this.selectTags.some(
-              tag => tag.id === this.tagGroupList[i].tag[x].id
-            );
+              if (!this.outSelectTags) {
+                this.outSelectTags = [];
+              }
 
-            // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
-            if (!tagExists) {
-              this.selectTags.push(this.tagGroupList[i].tag[x]);
+              // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+              let tagExists = this.outSelectTags.some(
+                tag => tag.id === this.tagGroupList[i].tag[x].id
+              );
+
+              // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+              if (!tagExists) {
+                this.outSelectTags.push(this.tagGroupList[i].tag[x]);
+              }
             }
           }
         }
+        if (!this.outSelectTags || this.outSelectTags.length === 0) {
+          return this.$message('请选择标签');
+        }
       }
-      if (!this.selectTags || this.selectTags.length === 0) {
-        return this.$message('请选择标签');
-      }
-
       this.changeTagDialog.open = false;
     },
 
@@ -1703,6 +1824,7 @@ export default {
       this.queryParams.corpId= this.myQwCompanyList[0].dictValue;
       this.queryParams.transferStatus = null;
       this.selectTags=[];
+      this.outSelectTags=[];
       this.createTime=null;
       this.queryParams.sTime=null;
       this.queryParams.eTime=null;

+ 140 - 28
src/views/qw/externalContact/index.vue

@@ -165,14 +165,6 @@
         </el-select>
       </el-form-item>
       <el-form-item label="标签" prop="tagIds">
-<!--        <el-select v-model="selectTags" remote multiple placeholder="请选择" filterable  style="width: 100%;">-->
-<!--          <el-option-->
-<!--            v-for="dict in tagList"-->
-<!--            :label="dict.name"-->
-<!--            :value="dict.tagId">-->
-<!--          </el-option>-->
-<!--        </el-select>-->
-
         <div @click="hangleChangeTags()" style="cursor: pointer; border: 1px solid #e6e6e6; background-color: white; overflow: hidden; flex-grow: 1;width: 250px">
           <div style="min-height: 35px; max-height: 200px; overflow-y: auto;">
             <el-tag type="success"
@@ -186,8 +178,23 @@
             </el-tag>
           </div>
         </div>
+      </el-form-item>
 
-
+      <el-form-item label="排除标签" prop="outTagIds">
+        <div @click="hangleChangeOutTags()"
+             style="cursor: pointer; border: 1px solid #e6e6e6; background-color: white; overflow: hidden; flex-grow: 1;width: 250px">
+          <div style="min-height: 35px; max-height: 200px; overflow-y: auto;">
+            <el-tag type="success"
+                    closable
+                    :disable-transitions="false"
+                    v-for="list in this.outSelectTags"
+                    :key="list.tagId"
+                    @close="handleCloseOutTags(list)"
+                    style="margin: 3px;"
+            >{{ list.name }}
+            </el-tag>
+          </div>
+        </div>
       </el-form-item>
       <el-form-item label="备注" prop="remark">
         <el-input
@@ -596,8 +603,8 @@
         @pagination="getPageListTagGroup"
       />
       <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="tagSubmitForm()">确 定</el-button>
-        <el-button @click="tagCancel()">取消</el-button>
+        <el-button type="primary" @click="tagSubmitForm(changeTagDialog.type)">确 定</el-button>
+        <el-button @click="tagCancel(changeTagDialog.type)">取消</el-button>
       </div>
     </el-dialog>
 
@@ -1097,6 +1104,7 @@ export default {
       changeTagDialog:{
         title:"",
         open:false,
+        type: null,
       },
 
       queryTagParams:{
@@ -1123,6 +1131,7 @@ export default {
         gender: null,
         description: null,
         tagIds: null,
+        outTagIds: null,
         remark:null,
         remarkMobiles: null,
         remarkCorpName: null,
@@ -1144,7 +1153,10 @@ export default {
         companyUser:null,
         userRepeat: null
       },
+      //选择的标签
       selectTags:[],
+      //排除的标签
+      outSelectTags:[],
       // 表单参数
       form: {},
       tagList:[],
@@ -1499,6 +1511,7 @@ export default {
 
       this.changeTagDialog.title="搜索的标签"
       this.changeTagDialog.open=true;
+      this.changeTagDialog.type = 1;
 
       // 获取 tagListFormIndex 中的所有 tagId,用于快速查找
       const selectedTagIds = new Set(
@@ -1517,6 +1530,31 @@ export default {
         }
       }, 200);
 
+    },
+
+
+    //选择排除标签
+    hangleChangeOutTags() {
+      this.changeTagDialog.title="搜索的标签"
+      this.changeTagDialog.open=true;
+      this.changeTagDialog.type = 2;
+
+      // 获取 tagListFormIndex 中的所有 tagId,用于快速查找
+      const selectedTagIds = new Set(
+        (this.outSelectTags || []).map(tagItem => tagItem?.tagId)
+      );
+
+      this.queryTagParams.name=null;
+
+      this.getPageListTagGroup();
+
+      setTimeout(() => {
+        for (let i = 0; i < this.tagGroupList.length; i++) {
+          for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+            this.tagGroupList[i].tag[x].isSelected = selectedTagIds.has(this.tagGroupList[i].tag[x].tagId);
+          }
+        }
+      }, 200);
 
     },
 
@@ -1549,6 +1587,35 @@ export default {
 
     },
 
+    //删除一些排除的标签
+    handleCloseOutTags(list){
+      const ls = this.outSelectTags.findIndex(t => t.tagId === list.tagId);
+      if (ls !== -1) {
+        this.outSelectTags.splice(ls, 1);
+        this.outSelectTags = [...this.selectTags];
+      }
+
+      if (this.outSelectTags!=null && this.outSelectTags.length>0){
+        // 确保 this.form.tags 是数组
+        if (!this.queryParams.outTagIds) {
+          this.queryParams.outTagIds = []; // 如果未定义,初始化
+        } else {
+          this.queryParams.outTagIds = []; // 清空已有数据
+        }
+
+        // 遍历并添加 tagId
+        this.outSelectTags.forEach(tag => {
+          if (tag.tagId) { // 确保 tagId 存在
+            this.queryParams.outTagIds.push(tag.tagId);
+          }
+        });
+        this.queryParams.outTagIds=this.queryParams.outTagIds.join(",");
+      }else {
+        this.queryParams.outTagIds=null;
+      }
+
+    },
+
     //重新获取页面数据
     refreshList(){
       this.getList();
@@ -1740,6 +1807,25 @@ export default {
       }
 
 
+      if (this.outSelectTags!=null && this.outSelectTags.length>0){
+        // 确保 this.form.tags 是数组
+        if (!this.queryParams.outTagIds) {
+          this.queryParams.outTagIds = []; // 如果未定义,初始化
+        } else {
+          this.queryParams.outTagIds = []; // 清空已有数据
+        }
+
+        // 遍历并添加 tagId
+        this.outSelectTags.forEach(tag => {
+          if (tag.tagId) { // 确保 tagId 存在
+            this.queryParams.outTagIds.push(tag.tagId);
+          }
+        });
+        this.queryParams.outTagIds=this.queryParams.outTagIds.join(",");
+      }else {
+        this.queryParams.outTagIds=null;
+      }
+
       this.queryParams.pageNum = 1;
       this.getList();
     },
@@ -1765,32 +1851,57 @@ export default {
       this.getPageListTagGroup();
     },
     //确定选择标签
-    tagSubmitForm(){
+    tagSubmitForm(type){
 
-      for (let i = 0; i < this.tagGroupList.length; i++) {
-        for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
-          if (this.tagGroupList[i].tag[x].isSelected === true) {
+      if (type==1){
+        for (let i = 0; i < this.tagGroupList.length; i++) {
+          for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+            if (this.tagGroupList[i].tag[x].isSelected === true) {
+
+              if (!this.selectTags) {
+                this.selectTags = [];
+              }
+
+              // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+              let tagExists = this.selectTags.some(
+                tag => tag.id === this.tagGroupList[i].tag[x].id
+              );
 
-            if (!this.selectTags) {
-              this.selectTags = [];
+              // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+              if (!tagExists) {
+                this.selectTags.push(this.tagGroupList[i].tag[x]);
+              }
             }
+          }
+        }
+        if (!this.selectTags || this.selectTags.length === 0) {
+          return this.$message('请选择标签');
+        }
+      }else if (type == 2) {
+        for (let i = 0; i < this.tagGroupList.length; i++) {
+          for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+            if (this.tagGroupList[i].tag[x].isSelected === true) {
 
-            // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
-            let tagExists = this.selectTags.some(
-              tag => tag.id === this.tagGroupList[i].tag[x].id
-            );
+              if (!this.outSelectTags) {
+                this.outSelectTags = [];
+              }
 
-            // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
-            if (!tagExists) {
-              this.selectTags.push(this.tagGroupList[i].tag[x]);
+              // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+              let tagExists = this.outSelectTags.some(
+                tag => tag.id === this.tagGroupList[i].tag[x].id
+              );
+
+              // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+              if (!tagExists) {
+                this.outSelectTags.push(this.tagGroupList[i].tag[x]);
+              }
             }
           }
         }
+        if (!this.outSelectTags || this.outSelectTags.length === 0) {
+          return this.$message('请选择标签');
+        }
       }
-      if (!this.selectTags || this.selectTags.length === 0) {
-        return this.$message('请选择标签');
-      }
-
       this.changeTagDialog.open = false;
     },
 
@@ -1815,6 +1926,7 @@ export default {
       this.queryParams.transferStatus=null;
       this.queryParams.corpId= this.myQwCompanyList[0].dictValue;
       this.selectTags=[];
+      this.outSelectTags=[];
 	   this.createTime=null;
 	  this.queryParams.sTime=null;
 	  this.queryParams.eTime=null;

+ 125 - 4
src/views/qw/externalContact/myExternalContact.vue

@@ -149,6 +149,22 @@
         </div>
 
       </el-form-item>
+      <el-form-item label="排除标签" prop="outTagIds">
+        <div @click="hangleChangeOutTags()"
+             style="cursor: pointer; border: 1px solid #e6e6e6; background-color: white; overflow: hidden; flex-grow: 1;width: 250px">
+          <div style="min-height: 35px; max-height: 200px; overflow-y: auto;">
+            <el-tag type="success"
+                    closable
+                    :disable-transitions="false"
+                    v-for="list in this.outSelectTags"
+                    :key="list.tagId"
+                    @close="handleCloseOutTags(list)"
+                    style="margin: 3px;"
+            >{{ list.name }}
+            </el-tag>
+          </div>
+        </div>
+      </el-form-item>
       <el-form-item label="备注" prop="remark">
         <el-input
           v-model="queryParams.remark"
@@ -533,7 +549,7 @@
       </el-table-column>
     </el-table>
 
-    <pagination
+    <pagination-more
       v-show="total>0"
       :total="total"
       :page.sync="queryParams.pageNum"
@@ -673,8 +689,8 @@
         @pagination="getPageListTagGroup"
       />
       <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="tagSubmitForm()">确 定</el-button>
-        <el-button @click="tagCancel()">取消</el-button>
+        <el-button type="primary" @click="tagSubmitForm(changeTagDialog.type)">确 定</el-button>
+        <el-button @click="tagCancel(changeTagDialog.type)">取消</el-button>
       </div>
     </el-dialog>
 
@@ -1192,6 +1208,7 @@ export default {
         gender: null,
         description: null,
         tagIds: null,
+        outTagIds: null,
         remark:null,
         remarkMobiles: null,
         remarkCorpName: null,
@@ -1225,8 +1242,11 @@ export default {
       changeTagDialog:{
         title:"",
         open:false,
+        type: null,
       },
       selectTags:[],
+      //排除的标签
+      outSelectTags:[],
       // 表单参数
       form: {},
       tagList:[],
@@ -1411,6 +1431,7 @@ export default {
 
       this.changeTagDialog.title="搜索的标签"
       this.changeTagDialog.open=true;
+      this.changeTagDialog.type = 1;
 
       // 获取 tagListFormIndex 中的所有 tagId,用于快速查找
       const selectedTagIds = new Set(
@@ -1431,9 +1452,36 @@ export default {
 
     },
 
+
+    //选择排除标签
+    hangleChangeOutTags() {
+      this.changeTagDialog.title="搜索的标签"
+      this.changeTagDialog.open=true;
+      this.changeTagDialog.type = 2;
+
+      // 获取 tagListFormIndex 中的所有 tagId,用于快速查找
+      const selectedTagIds = new Set(
+        (this.outSelectTags || []).map(tagItem => tagItem?.tagId)
+      );
+
+      this.queryTagParams.name=null;
+
+      this.getPageListTagGroup();
+
+      setTimeout(() => {
+        for (let i = 0; i < this.tagGroupList.length; i++) {
+          for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+            this.tagGroupList[i].tag[x].isSelected = selectedTagIds.has(this.tagGroupList[i].tag[x].tagId);
+          }
+        }
+      }, 200);
+
+    },
+
     //确定选择标签
-    tagSubmitForm(){
+    tagSubmitForm(type){
 
+      if (type==1) {
         for (let i = 0; i < this.tagGroupList.length; i++) {
           for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
             if (this.tagGroupList[i].tag[x].isSelected === true) {
@@ -1457,7 +1505,31 @@ export default {
         if (!this.selectTags || this.selectTags.length === 0) {
           return this.$message('请选择标签');
         }
+      }else if (type == 2) {
+        for (let i = 0; i < this.tagGroupList.length; i++) {
+          for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+            if (this.tagGroupList[i].tag[x].isSelected === true) {
+
+              if (!this.outSelectTags) {
+                this.outSelectTags = [];
+              }
+
+              // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+              let tagExists = this.outSelectTags.some(
+                tag => tag.id === this.tagGroupList[i].tag[x].id
+              );
 
+              // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+              if (!tagExists) {
+                this.outSelectTags.push(this.tagGroupList[i].tag[x]);
+              }
+            }
+          }
+        }
+        if (!this.outSelectTags || this.outSelectTags.length === 0) {
+          return this.$message('请选择标签');
+        }
+      }
       this.changeTagDialog.open = false;
     },
 
@@ -1495,6 +1567,35 @@ export default {
 
     },
 
+    //删除一些排除的标签
+    handleCloseOutTags(list){
+      const ls = this.outSelectTags.findIndex(t => t.tagId === list.tagId);
+      if (ls !== -1) {
+        this.outSelectTags.splice(ls, 1);
+        this.outSelectTags = [...this.selectTags];
+      }
+
+      if (this.outSelectTags!=null && this.outSelectTags.length>0){
+        // 确保 this.form.tags 是数组
+        if (!this.queryParams.outTagIds) {
+          this.queryParams.outTagIds = []; // 如果未定义,初始化
+        } else {
+          this.queryParams.outTagIds = []; // 清空已有数据
+        }
+
+        // 遍历并添加 tagId
+        this.outSelectTags.forEach(tag => {
+          if (tag.tagId) { // 确保 tagId 存在
+            this.queryParams.outTagIds.push(tag.tagId);
+          }
+        });
+        this.queryParams.outTagIds=this.queryParams.outTagIds.join(",");
+      }else {
+        this.queryParams.outTagIds=null;
+      }
+
+    },
+
     handleChangeStatus(row) {
       this.statusForm = {
         id: row.id,
@@ -1884,6 +1985,25 @@ export default {
       }
 
 
+      if (this.outSelectTags!=null && this.outSelectTags.length>0){
+        // 确保 this.form.tags 是数组
+        if (!this.queryParams.outTagIds) {
+          this.queryParams.outTagIds = []; // 如果未定义,初始化
+        } else {
+          this.queryParams.outTagIds = []; // 清空已有数据
+        }
+
+        // 遍历并添加 tagId
+        this.outSelectTags.forEach(tag => {
+          if (tag.tagId) { // 确保 tagId 存在
+            this.queryParams.outTagIds.push(tag.tagId);
+          }
+        });
+        this.queryParams.outTagIds=this.queryParams.outTagIds.join(",");
+      }else {
+        this.queryParams.outTagIds=null;
+      }
+
       this.queryParams.pageNum = 1;
       this.getList();
     },
@@ -1894,6 +2014,7 @@ export default {
       this.queryParams.corpId=this.myQwUserList[0].corpId;
       this.queryParams.transferStatus = null;
       this.selectTags=[];
+      this.outSelectTags=[];
 	   this.createTime=null;
 	  this.queryParams.sTime=null;
 	  this.queryParams.eTime=null;

+ 10 - 1
src/views/qw/externalContactLoss/deptLossIndex.vue

@@ -30,7 +30,14 @@ deptLossIndex<template>
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-
+      <el-form-item label="添加时间" prop="createTime">
+        <el-date-picker clearable size="small"
+                        v-model="queryParams.createTime"
+                        type="date"
+                        value-format="yyyy-MM-dd"
+                        placeholder="添加时间">
+        </el-date-picker>
+      </el-form-item>
 
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@@ -76,6 +83,7 @@ deptLossIndex<template>
           <dict-tag :options="typeOptions" :value="scope.row.type"/>
         </template>
       </el-table-column>
+      <el-table-column label="添加时间" align="center" prop="createTime"/>
        <el-table-column label="流失时间" align="center" prop="lossTime" v-if="queryParams.status==3"/>
        <el-table-column label="删除时间" align="center" prop="delTime" v-if="queryParams.status==4"/>
       <el-table-column label="状态" align="center" prop="status" width="100px">
@@ -239,6 +247,7 @@ export default {
         name: null,
         avatar: null,
         type: null,
+        createTime:null,
         gender: null,
         description: null,
         tagIds: null,

+ 10 - 1
src/views/qw/externalContactLoss/index.vue

@@ -39,7 +39,14 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-
+      <el-form-item label="添加时间" prop="createTime">
+        <el-date-picker clearable size="small"
+                        v-model="queryParams.createTime"
+                        type="date"
+                        value-format="yyyy-MM-dd"
+                        placeholder="添加时间">
+        </el-date-picker>
+      </el-form-item>
 
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@@ -85,6 +92,7 @@
           <dict-tag :options="typeOptions" :value="scope.row.type"/>
         </template>
       </el-table-column>
+      <el-table-column label="添加时间" align="center" prop="createTime"/>
        <el-table-column label="流失时间" align="center" prop="lossTime" v-if="queryParams.status==3"/>
        <el-table-column label="删除时间" align="center" prop="delTime" v-if="queryParams.status==4"/>
       <el-table-column label="状态" align="center" prop="status" width="100px">
@@ -247,6 +255,7 @@ export default {
         externalUserId: null,
         name: null,
         avatar: null,
+        createTime:null,
         type: null,
         gender: null,
         description: null,

+ 10 - 0
src/views/qw/externalContactLoss/my.vue

@@ -31,6 +31,14 @@
         />
       </el-form-item>
 
+      <el-form-item label="添加时间" prop="createTime">
+        <el-date-picker clearable size="small"
+                        v-model="queryParams.createTime"
+                        type="date"
+                        value-format="yyyy-MM-dd"
+                        placeholder="添加时间">
+        </el-date-picker>
+      </el-form-item>
 
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@@ -66,6 +74,7 @@
           <dict-tag :options="typeOptions" :value="scope.row.type"/>
         </template>
       </el-table-column>
+      <el-table-column label="添加时间" align="center" prop="createTime"/>
        <el-table-column label="流失时间" align="center" prop="lossTime" v-if="queryParams.status==3"/>
        <el-table-column label="删除时间" align="center" prop="delTime" v-if="queryParams.status==4"/>
       <el-table-column label="状态" align="center" prop="status" width="100px">
@@ -228,6 +237,7 @@ export default {
         externalUserId: null,
         name: null,
         avatar: null,
+        createTime:null,
         type: null,
         gender: null,
         description: null,

+ 92 - 67
src/views/qw/friendWelcome/indexNew.vue

@@ -484,7 +484,7 @@
         </div>
         <div v-if="welcomeItem.type==='miniprogram'">
 
-          <el-form-item label="选择课程">
+          <el-form-item label="选择课程" prop="miniprogramCourseId">
             <el-select  v-model="fileFrom.courseId" placeholder="请选择课程" style=" margin-right: 10px;" size="mini"  @change="courseChange(fileFrom,welcomeItem.index,welcomeItem.itemIndex)">
               <el-option
                 v-for="dict in courseList"
@@ -532,7 +532,7 @@
         </div>
       </el-form>
       <div slot="footer" class="dialog-footer" style="text-align: center">
-        <el-button type="primary" @click="confirmUpload">确定</el-button>
+        <el-button type="primary" @click="confirmUpload('fileFrom')">确定</el-button>
         <el-button type="primary" @click="cancelUpload">取消</el-button>
       </div>
     </el-dialog>
@@ -593,6 +593,24 @@ export default {
         linkTitle:[ { required: true, message: "图文标题不能为空", trigger: "submit" }],
         linkUrl:[ { required: true, message: "图文链接不能为空", trigger: "submit" }],
         miniprogramTitle:[ { required: true, message: "图文链接不能为空", trigger: "submit" }],
+        miniprogramCourseId:[
+          {
+            required: true,
+            validator: (rule, value, callback) => {
+              // 检查id是否为空
+              if (!this.fileFrom.courseId) {
+                return callback(new Error('请选择课程'));
+              }
+              // 检查ddId是否为空
+              if (!this.fileFrom.videoId) {
+                return callback(new Error('请选择小节'));
+              }
+              // 校验通过
+              callback();
+            },
+            trigger: 'change' // 下拉选择变化时触发校验
+          }
+        ]
       },
 
 
@@ -999,72 +1017,79 @@ export default {
 
     //提交附件
     confirmUpload(fileFrom) {
-      const { type, index, itemIndex } = this.welcomeItem;
-      let attachment = {};
-      if (type === 'image') {
-        attachment = {
-          msgtype: 'image',
-          image: {
-            pic_url: this.fileFrom.imagePicUrl
-          }
-        };
-      } else if (type === 'video') {
-        attachment = {
-          msgtype: 'video',
-          video: {
-            url:this.fileFrom.videoUrl,
-          }
-        };
-      } else if (type === 'link') {
-        attachment = {
-          msgtype: 'link',
-          link: {
-            title: this.fileFrom.linkTitle,
-            picurl: this.fileFrom.linkPicUrl,
-            desc: this.fileFrom.linkDesc,
-            url: this.fileFrom.linkUrl,
-            courseId:this.fileFrom.courseId,
-            videoId:this.fileFrom.videoId,
-            expiresDays:this.fileFrom.expiresDays,
-          }
-        };
-      }else if (type==='miniprogram'){
-        attachment = {
-          msgtype: 'miniprogram',
-          miniprogram: {
-            title: this.fileFrom.miniprogramTitle,
-            pic_media_id: "待查询",
-            appid: "wx73f85f8d62769119",
-            page: this.fileFrom.miniprogramPage,
-            courseId:this.fileFrom.courseId,
-            videoId:this.fileFrom.videoId,
-            expiresDays:this.fileFrom.expiresDays,
-          }
-        };
-      }
-
-      if (itemIndex === -1) {
-        // 默认欢迎语附件处理
-        if (index < this.form.attachments.length) {
-          // 存在附件则更新
-          this.form.attachments.splice(index, 1, attachment);
-        } else {
-          // 不存在附件则插入
-          this.form.attachments.push(attachment);
-        }
-
-      } else {
-        // 分时段欢迎语附件处理
-        if (index < this.form.daypartingItemlist[itemIndex].attachments.length) {
-          // 存在附件则更新
-          this.form.daypartingItemlist[itemIndex].attachments.splice(index, 1, attachment);
-        } else {
-          // 不存在附件则插入
-          this.form.daypartingItemlist[itemIndex].attachments.push(attachment);
-        }
-      }
+     this.$refs[fileFrom].validate((valid) => {
+          if (valid) {
+            const { type, index, itemIndex } = this.welcomeItem;
+            let attachment = {};
+            if (type === 'image') {
+              attachment = {
+                msgtype: 'image',
+                image: {
+                  pic_url: this.fileFrom.imagePicUrl
+                }
+              };
+            } else if (type === 'video') {
+              attachment = {
+                msgtype: 'video',
+                video: {
+                  url:this.fileFrom.videoUrl,
+                }
+              };
+            } else if (type === 'link') {
+              attachment = {
+                msgtype: 'link',
+                link: {
+                  title: this.fileFrom.linkTitle,
+                  picurl: this.fileFrom.linkPicUrl,
+                  desc: this.fileFrom.linkDesc,
+                  url: this.fileFrom.linkUrl,
+                  courseId:this.fileFrom.courseId,
+                  videoId:this.fileFrom.videoId,
+                  expiresDays:this.fileFrom.expiresDays,
+                }
+              };
+            }else if (type==='miniprogram'){
+              attachment = {
+                msgtype: 'miniprogram',
+                miniprogram: {
+                  title: this.fileFrom.miniprogramTitle,
+                  pic_media_id: "待查询",
+                  appid: "wx73f85f8d62769119",
+                  page: this.fileFrom.miniprogramPage,
+                  courseId:this.fileFrom.courseId,
+                  videoId:this.fileFrom.videoId,
+                  expiresDays:this.fileFrom.expiresDays,
+                }
+              };
+            }
 
-      this.resetFileFrom();
+            if (itemIndex === -1) {
+              // 默认欢迎语附件处理
+              if (index < this.form.attachments.length) {
+                // 存在附件则更新
+                this.form.attachments.splice(index, 1, attachment);
+              } else {
+                // 不存在附件则插入
+                this.form.attachments.push(attachment);
+              }
+
+            } else {
+              // 分时段欢迎语附件处理
+              if (index < this.form.daypartingItemlist[itemIndex].attachments.length) {
+                // 存在附件则更新
+                this.form.daypartingItemlist[itemIndex].attachments.splice(index, 1, attachment);
+              } else {
+                // 不存在附件则插入
+                this.form.daypartingItemlist[itemIndex].attachments.push(attachment);
+              }
+            }
+            this.resetFileFrom();
+          } else {
+            console.log('error submit!!');
+            return false;
+          }
+        });
+     
     },
 
     //取消附件

+ 92 - 66
src/views/qw/friendWelcome/myWelcome.vue

@@ -481,7 +481,7 @@
         </div>
         <div v-if="welcomeItem.type==='miniprogram'">
 
-          <el-form-item label="选择课程">
+          <el-form-item label="选择课程" prop="miniprogramCourseId">
             <el-select  v-model="fileFrom.courseId" placeholder="请选择课程" style=" margin-right: 10px;" size="mini"  @change="courseChange(fileFrom,welcomeItem.index,welcomeItem.itemIndex)">
               <el-option
                 v-for="dict in courseList"
@@ -529,7 +529,7 @@
         </div>
       </el-form>
       <div slot="footer" class="dialog-footer" style="text-align: center">
-        <el-button type="primary" @click="confirmUpload">确定</el-button>
+        <el-button type="primary" @click="confirmUpload('fileFrom')">确定</el-button>
         <el-button type="primary" @click="cancelUpload">取消</el-button>
       </div>
     </el-dialog>
@@ -591,6 +591,24 @@ export default {
         linkTitle:[ { required: true, message: "图文标题不能为空", trigger: "submit" }],
         linkUrl:[ { required: true, message: "图文链接不能为空", trigger: "submit" }],
         miniprogramTitle:[ { required: true, message: "图文链接不能为空", trigger: "submit" }],
+        miniprogramCourseId:[
+          {
+            required: true,
+            validator: (rule, value, callback) => {
+              // 检查id是否为空
+              if (!this.fileFrom.courseId) {
+                return callback(new Error('请选择课程'));
+              }
+              // 检查ddId是否为空
+              if (!this.fileFrom.videoId) {
+                return callback(new Error('请选择小节'));
+              }
+              // 校验通过
+              callback();
+            },
+            trigger: 'change' // 下拉选择变化时触发校验
+          }
+        ]
       },
 
 
@@ -999,73 +1017,81 @@ export default {
 
     //提交附件
     confirmUpload(fileFrom) {
+    this.$refs[fileFrom].validate((valid) => {
+      if (valid) {
+        const { type, index, itemIndex } = this.welcomeItem;
+            let attachment = {};
+            if (type === 'image') {
+              attachment = {
+                msgtype: 'image',
+                image: {
+                  pic_url: this.fileFrom.imagePicUrl
+                }
+              };
+            } else if (type === 'video') {
+              attachment = {
+                msgtype: 'video',
+                video: {
+                  url:this.fileFrom.videoUrl,
+                }
+              };
+            } else if (type === 'link') {
+              attachment = {
+                msgtype: 'link',
+                link: {
+                  title: this.fileFrom.linkTitle,
+                  picurl: this.fileFrom.linkPicUrl,
+                  desc: this.fileFrom.linkDesc,
+                  url: this.fileFrom.linkUrl,
+                  courseId:this.fileFrom.courseId,
+                  videoId:this.fileFrom.videoId,
+                  expiresDays:this.fileFrom.expiresDays,
+                }
+              };
+            }else if (type==='miniprogram'){
+              attachment = {
+                msgtype: 'miniprogram',
+                miniprogram: {
+                  title: this.fileFrom.miniprogramTitle,
+                  pic_media_id: "待查询",
+                  appid: "wx73f85f8d62769119",
+                  page: this.fileFrom.miniprogramPage,
+                  courseId:this.fileFrom.courseId,
+                  videoId:this.fileFrom.videoId,
+                  expiresDays:this.fileFrom.expiresDays,
+                }
+              };
+            }
 
-      const { type, index, itemIndex } = this.welcomeItem;
-      let attachment = {};
-      if (type === 'image') {
-        attachment = {
-          msgtype: 'image',
-          image: {
-            pic_url: this.fileFrom.imagePicUrl
-          }
-        };
-      } else if (type === 'video') {
-        attachment = {
-          msgtype: 'video',
-          video: {
-            url:this.fileFrom.videoUrl,
-          }
-        };
-      } else if (type === 'link') {
-        attachment = {
-          msgtype: 'link',
-          link: {
-            title: this.fileFrom.linkTitle,
-            picurl: this.fileFrom.linkPicUrl,
-            desc: this.fileFrom.linkDesc,
-            url: this.fileFrom.linkUrl,
-            courseId:this.fileFrom.courseId,
-            videoId:this.fileFrom.videoId,
-            expiresDays:this.fileFrom.expiresDays,
-          }
-        };
-      }else if (type==='miniprogram'){
-        attachment = {
-          msgtype: 'miniprogram',
-          miniprogram: {
-            title: this.fileFrom.miniprogramTitle,
-            pic_media_id: "待查询",
-            appid: "wx73f85f8d62769119",
-            page: this.fileFrom.miniprogramPage,
-            courseId:this.fileFrom.courseId,
-            videoId:this.fileFrom.videoId,
-            expiresDays:this.fileFrom.expiresDays,
-          }
-        };
-      }
-
-      if (itemIndex === -1) {
-        // 默认欢迎语附件处理
-        if (index < this.form.attachments.length) {
-          // 存在附件则更新
-          this.form.attachments.splice(index, 1, attachment);
-        } else {
-          // 不存在附件则插入
-          this.form.attachments.push(attachment);
-        }
+            if (itemIndex === -1) {
+              // 默认欢迎语附件处理
+              if (index < this.form.attachments.length) {
+                // 存在附件则更新
+                this.form.attachments.splice(index, 1, attachment);
+              } else {
+                // 不存在附件则插入
+                this.form.attachments.push(attachment);
+              }
+
+            } else {
+              // 分时段欢迎语附件处理
+              if (index < this.form.daypartingItemlist[itemIndex].attachments.length) {
+                // 存在附件则更新
+                this.form.daypartingItemlist[itemIndex].attachments.splice(index, 1, attachment);
+              } else {
+                // 不存在附件则插入
+                this.form.daypartingItemlist[itemIndex].attachments.push(attachment);
+              }
+            }
 
-      } else {
-        // 分时段欢迎语附件处理
-        if (index < this.form.daypartingItemlist[itemIndex].attachments.length) {
-          // 存在附件则更新
-          this.form.daypartingItemlist[itemIndex].attachments.splice(index, 1, attachment);
-        } else {
-          // 不存在附件则插入
-          this.form.daypartingItemlist[itemIndex].attachments.push(attachment);
+            this.resetFileFrom();
+        }else {
+          console.log('error submit!!');
+          return false;
         }
-      }
-
-      this.resetFileFrom();
+          
+      });
+      
     },
 
     //取消附件

+ 102 - 17
src/views/qw/sop/addSop.vue

@@ -70,7 +70,55 @@
           </div>
           <Tip title="企业微信员工账号" />
         </el-form-item>
-        <el-form-item label="群聊" prop="chatIds" v-if="form.filterMode == 2">
+        <el-form-item label="是否自动创建群" prop="autoGroup" v-if="form.filterMode == 2">
+          <el-radio-group v-model="form.autoGroup">
+            <el-radio
+              :label="0"
+            >否
+            </el-radio>
+            <el-radio
+              :label="1"
+            >是
+            </el-radio>
+          </el-radio-group>
+          <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+            <i class="el-icon-info"></i>
+            选择的企业微信员工下面的群聊
+          </div>
+        </el-form-item>
+        <el-form-item label="是否注册用户" prop="autoGroup" v-if="form.filterMode == 2 && form.autoGroup == 1">
+          <el-radio-group v-model="form.autoUserReg">
+            <el-radio
+              :label="0"
+            >否
+            </el-radio>
+            <el-radio
+              :label="1"
+            >是
+            </el-radio>
+          </el-radio-group>
+          <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+            <i class="el-icon-info"></i>
+            筛选是否注册的用户
+          </div>
+        </el-form-item>
+        <el-form-item label="创建群聊评级" prop="chatIds" v-if="form.filterMode == 2 && form.autoGroup == 1">
+          <el-select multiple filterable clearable v-model="form.autoGroupLevelArray">
+            <el-option v-for="item in levelList" :key="item.value" :label="item.label" :value="item.value"/>
+          </el-select>
+          <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+            <i class="el-icon-info"></i>
+            选择需要拉取的客户评级
+          </div>
+        </el-form-item>
+        <el-form-item label="群聊名称" prop="chatIds" v-if="form.filterMode == 2 && form.autoGroup == 1">
+          <el-input v-model="form.groupName" maxlength="10" placeholder="请输入群聊名称" />
+          <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+            <i class="el-icon-info"></i>
+            创建群聊的名称(如有重复自动加数字区分)
+          </div>
+        </el-form-item>
+        <el-form-item label="群聊" prop="chatIds" v-if="form.filterMode == 2 && form.autoGroup == 0">
           <el-select multiple filterable clearable v-model="form.chatIds">
             <el-option v-for="item in qwGroupList" :key="item.chatId" :label="item.name" :value="item.chatId"/>
           </el-select>
@@ -162,13 +210,6 @@
             <Tip title="选择进入SOP任务的标签" />
           </el-form-item>
           <el-form-item label="排除的标签" prop="excludeTags">
-            <!--            <el-select v-model="excludeTags" remote multiple placeholder="请选择" filterable  style="width: 100%;">-->
-            <!--              <el-option-->
-            <!--                v-for="dict in tagList"-->
-            <!--                :label="dict.name"-->
-            <!--                :value="dict.tagId">-->
-            <!--              </el-option>-->
-            <!--            </el-select>-->
             <div @click="hangleChangeOutTags()"
                  style="cursor: pointer; border: 1px solid #e6e6e6; background-color: white; overflow: hidden; flex-grow: 1;width: 390px">
               <div style="min-height: 35px; max-height: 200px; overflow-y: auto;">
@@ -347,7 +388,7 @@
         </el-form-item>
 
         <el-form-item label="模板" prop="tempId">
-          <div @click="selectListSopTemp(form.sendType)"
+          <div @click="selectListSopTemp(form.sendType,formType)"
                style="cursor: pointer; border: 1px solid #e6e6e6; background-color: white; overflow: hidden; flex-grow: 1;">
             <el-tag v-if="form.tempId" type="success" style="margin: 3px;">
               {{ form.tempName }}
@@ -526,7 +567,14 @@ export default {
       companyUserLists: [],
       // 状态字典
       statusOptions: [],
-
+      levelList: [
+        {value: 1, label: "A"},
+        {value: 2, label: "B"},
+        {value: 3, label: "C"},
+        {value: 4, label: "D"},
+        {value: 5, label: "E"},
+        {value: -1, label: "未评级"}
+      ],
       //企微SOP发送类型
       sysQwSopType: [],
       projectOptions: [],
@@ -544,6 +592,11 @@ export default {
         isFixed: 0,
         isRegister: 0,
         type: 2,
+        autoGroup:0,
+        autoUserReg:0,
+        autoGroupLevelArray:[],
+        autoGroupLevel:null,
+        groupName:null,
         filterType: 2,
         expiryTime: 4,
         isAutoSop: 1,
@@ -571,12 +624,15 @@ export default {
         startTime: [{required: true, message: "开始时间不能为空", trigger: "submit"}],
         tempId: [{required: true, message: "模板不能为空", trigger: "submit"}],
         openCommentStatus:[{ required: true, message: '开启评论/弹幕不能为空', trigger: 'change' }]
-      }
+      },
+      //1 总 2 我的 3 部门
+      formType: null,
     };
   },
   created() {
 
     this.form.corpId = this.$route.params && this.$route.params.corpId;
+    this.formType = this.$route.params && this.$route.params.type;
 
     this.queryTagParams.corpId = this.$route.params && this.$route.params.corpId;
 
@@ -786,13 +842,13 @@ export default {
       }
     },
     //查询模板
-    selectListSopTemp(type) {
+    selectListSopTemp(type,formType) {
       if(this.form.filterMode == 2){
         type = 11;
       }
       this.tempOpen = true;
       setTimeout(() => {
-        this.$refs.SopTempComments.getList(type, undefined, this.form.type == 3);
+        this.$refs.SopTempComments.getList(type, undefined, this.form.type == 3,formType);
       }, 200);
 
     },
@@ -888,6 +944,11 @@ export default {
         filterType: 2,
         expiryTime: 4,
         qwUserIds: null,
+        autoGroup:0,
+        autoGroupLevelArray:[],
+        autoGroupLevel:null,
+        groupName:null,
+        autoUserReg:0,
         corpId: null,
         setting: null,
         createBy: null,
@@ -942,10 +1003,21 @@ export default {
         if (valid) {
           if (this.form.type == 2 && this.form.filterMode == 2) {
             this.form.sendType = 11;
-            if (this.form.chatIds.length <= 0) {
-              return this.$message.error("请选择群聊")
+            if(this.form.autoGroup == 0){
+              if (this.form.chatIds.length <= 0) {
+                return this.$message.error("请选择群聊")
+              }
+            }
+            if(this.form.autoGroup == 1){
+              if (this.form.autoGroupLevelArray.length <= 0) {
+                return this.$message.error("请选择评级")
+              }
+              if (this.form.groupName == null) {
+                return this.$message.error("请输入群聊名称")
+              }
             }
             this.form.chatId = this.form.chatIds.join();
+            this.form.autoGroupLevel = this.form.autoGroupLevelArray.join();
           } else {
             if (this.userSelectList.length <= 0) {
               return this.$message.error("请选择员工")
@@ -1000,7 +1072,14 @@ export default {
               this.msgSuccess("修改成功");
               this.$store.dispatch("tagsView/delView", this.$route);
               // this.$router.replace('/qw/conversion/sop')
-              window.location.replace('/qw/conversion/sop')
+              if (this.formType==1){
+                window.location.replace('/qw/conversion/sop')
+              }else if (this.formType==2){
+                window.location.replace('/qw/conversion/mySop')
+              }else if (this.formType==3){
+                window.location.replace('/qw/conversion/deptSop')
+              }
+
               this.reset();
             });
           } else {
@@ -1008,7 +1087,13 @@ export default {
               this.msgSuccess("新增成功");
               this.$store.dispatch("tagsView/delView", this.$route);
               // this.$router.replace('/qw/conversion/sop')
-              window.location.replace('/qw/conversion/sop')
+              if (this.formType==1){
+                window.location.replace('/qw/conversion/sop')
+              }else if (this.formType==2){
+                window.location.replace('/qw/conversion/mySop')
+              }else if (this.formType==3){
+                window.location.replace('/qw/conversion/deptSop')
+              }
               this.reset();
             });
           }

+ 123 - 40
src/views/qw/sop/deptSop.vue

@@ -104,6 +104,18 @@
         >删除
         </el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleSopLogsDelete"
+          v-hasPermi="['qw:sopLogs:removeAll']"
+        >批量删除执行任务
+        </el-button>
+      </el-col>
       <el-col :span="1.5">
         <el-button
           type="success"
@@ -128,6 +140,18 @@
         >批量执行SOP
         </el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-tooltip class="item" effect="dark" content="此功能用于给 选中的 SOP任务营期 内【所有的】客户发送 消息【或者发送草稿-/-清楚草稿】" placement="top">
+          <el-button
+            type="warning"
+            icon="el-icon-s-promotion"
+            size="mini"
+            :disabled="multiple"
+            @click="handleCampSendMsg"
+            v-hasPermi="['qw:sopUserLogsInfo:msgSop']"
+          >SOP营期一键群发(或草稿)</el-button>
+        </el-tooltip>
+      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
     <el-table v-loading="loading" border :data="sopList" @selection-change="handleSelectionChange">
@@ -313,6 +337,7 @@
       @pagination="getList"
     />
 
+    <send-msg-sop-open-tool ref="sendMsgSopOpenTool" ></send-msg-sop-open-tool>
     <!-- 添加或修改企微sop对话框 -->
     <el-dialog :title="title" :visible.sync="open" width="1000px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="100px">
@@ -734,7 +759,20 @@
                       </el-card>
                     </div>
                     <div v-if="item.contentType == 4">
-
+                      <el-card class="box-card">
+                        <el-form-item label="标题" prop="miniprogramTitle">
+                          <el-input v-model="item.miniprogramTitle" placeholder="请输入小程序消息标题,最长为64字"  />
+                        </el-form-item>
+                        <el-form-item label="封面" prop="miniprogramPicUrl">
+                          <ImageUpload v-model="item.miniprogramPicUrl"  type="image" :num="10" :width="150" :height="150" />
+                        </el-form-item>
+                        <el-form-item label="appid" prop="miniprogramAppid" v-show="false" >
+                          <el-input v-model="item.miniprogramAppid" disabled />
+                        </el-form-item>
+                        <el-form-item label="page路径" prop="miniprogramPage" v-show="false" label-width="100px" style="margin-left: -30px" >
+                          <el-input v-model="item.miniprogramPage" placeholder="小程序消息打开后的路径"  disabled />
+                        </el-form-item>
+                      </el-card>
                     </div>
                     <div v-if="item.contentType == 5 ">
 
@@ -853,7 +891,7 @@
 import {
   addSop,
   courseList,
-  delSop,
+  delSop, delSopLogs,
   exportSop,
   getSopVoiceList, listDeptSop,
   listSop,
@@ -873,10 +911,12 @@ import sopLogsDetails from '@/views/qw/sopLogs/sopLogsList.vue'
 import {listTag,} from "@/api/qw/tag";
 import {getMyQwCompanyList} from "@/api/qw/user";
 import {allList} from "@/api/qw/groupChat";
+import SendMsgSopOpenTool from '@/views/qw/sopUserLogsInfo/sendMsgSopOpenTool.vue'
+import {getQwDeptUserList} from "../../../api/company/companyUser";
 
 export default {
   name: "Sop",
-  components: {CustomerGroupDetails, qwUserList, ImageUpload, sopLogsDetails},
+  components: {CustomerGroupDetails, qwUserList, ImageUpload, sopLogsDetails, SendMsgSopOpenTool},
   data() {
     return {
       // 存储每一行的展开状态
@@ -1071,6 +1111,16 @@ export default {
     }
   },
   methods: {
+    /**
+     * SOP任务营期一键群发
+     */
+    handleCampSendMsg(){
+
+      setTimeout(() => {
+        this.$refs.sendMsgSopOpenTool.oneClickGroupSending(this.ids,2,this.queryParams.corpId);
+      }, 500);
+
+    },
     voice(id) {
       this.voiceForm.queryParams.id = id;
       getSopVoiceList(this.voiceForm.queryParams).then(res => {
@@ -1174,7 +1224,7 @@ export default {
 
     //刷新部分数据
     refreshData(row) {
-      getQwAllUserList(row).then(response => {
+      getQwDeptUserList(row).then(response => {
         this.companyUserList = response.data;
       });
 
@@ -1342,9 +1392,9 @@ export default {
       });
     },
 
-    addSetList() {
+    addSetList(){
       const newSetting = {
-        contentType: '1',
+        contentType:'1',
         value: '',
       };
       // 将新设置项添加到 content.setting 数组中
@@ -1355,7 +1405,7 @@ export default {
     handleContentTypeChange() {
 
       //如果是链接的才上
-      if (this.msgForm.courseId != null) {
+      if (this.msgForm.courseId != null ) {
         const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === this.msgForm.courseId);
         for (let i = 0; i < this.setting.length; i++) {
           //响应式直接给链接的标题/封面上值
@@ -1363,6 +1413,9 @@ export default {
             this.$set(this.setting[i], 'linkTitle', selectedCourse.dictLabel);
             this.$set(this.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
           }
+          if (selectedCourse && this.setting[i].contentType == 4 && this.msgForm.courseId != null) {
+            this.$set(this.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+          }
 
         }
 
@@ -1373,12 +1426,20 @@ export default {
 
         for (let i = 0; i < this.setting.length; i++) {
           //响应式直接给链接的描述上值
-          if (selectedVideo && this.setting[i].contentType == 3 && this.msgForm.videoId != null) {
-            this.$set(this.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+          if (selectedVideo && this.msgForm.videoId != null) {
+            console.log(2, this.setting[i].contentType)
+            if (this.setting[i].contentType == 3 || this.setting[i].contentType == 9) {
+              this.$set(this.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+            }
+            if (this.setting[i].contentType == 4) {
+              this.$set(this.setting[i], 'miniprogramTitle', selectedVideo.dictLabel);
+            }
+
           }
         }
       }
 
+
     },
     videoIdChange() {
       if (this.msgForm.videoId != null) {
@@ -1387,8 +1448,16 @@ export default {
 
         for (let i = 0; i < this.setting.length; i++) {
           //响应式直接给链接的描述上值
-          if (selectedVideo && this.setting[i].contentType == 3 && this.msgForm.videoId != null) {
-            this.$set(this.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+          if (selectedVideo && this.msgForm.videoId != null) {
+            if (this.setting[i].contentType == 3 || this.setting[i].contentType == 9) {
+              this.$set(this.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+            }
+
+            if (this.setting[i].contentType == 4) {
+              this.$set(this.setting[i], 'miniprogramTitle', selectedVideo.dictLabel);
+            }
+
+
           }
         }
       }
@@ -1420,7 +1489,7 @@ export default {
         createBy: null,
         createTime: null,
         isAutoSop: null,
-        autoSopTime: { autoSopType: 2, autoStartTime: '00:00', autoEndTime: '24:00', autoSopSend: 2 },
+        autoSopTime: {autoSopType: 2, autoStartTime: '00:00', autoEndTime: '24:00', autoSopSend: 2},
       };
       this.resetForm("form");
       this.tags = null;
@@ -1448,7 +1517,7 @@ export default {
     /** 新增按钮操作 */
     handleAdd() {
       this.reset();
-      this.$router.push('/qw/sop/addSop/' + this.queryParams.corpId)
+      this.$router.push('/qw/sop/addSop/' + this.queryParams.corpId+"/"+3)
       // this.open = true;
       // this.setting=[]
       // this.userSelectList=[]
@@ -1469,7 +1538,7 @@ export default {
         tempId: row.tempId,
         filterMode: row.filterMode,
         corpId: row.corpId,
-        type:1,
+        type: 1,
       }
       // 使用 params 传递参数
       this.$router.push({
@@ -1496,7 +1565,7 @@ export default {
         this.msgError(res.msg);
       }
     },
-    beforeAvatarUploadFile(file) {
+    beforeAvatarUploadFile(file){
       const isLt1M = file.size / 1024 / 1024 < 10;
       if (!isLt1M) {
         this.$message.error('上传大小不能超过 10MB!');
@@ -1510,7 +1579,7 @@ export default {
     },
 
     handleAvatarSuccessVideo(res, file, item) {
-      if (res.code == 200) {
+      if(res.code==200){
         // 使用 $set 确保响应式更新
         this.$set(item, 'videoUrl', res.url);
       } else {
@@ -1518,7 +1587,7 @@ export default {
       }
     },
 
-    beforeAvatarUploadVideo(file) {
+    beforeAvatarUploadVideo(file){
       const isLt30M = file.size / 1024 / 1024 < 10;
       const isMP4 = file.type === 'video/mp4';
 
@@ -1535,7 +1604,7 @@ export default {
       return true;
     },
 
-    handleInputVideoText(value, content) {
+    handleInputVideoText(value,content){
       // 允许的字符:中文、英文(大小写)、数字和指定标点符号(,。!?)
       const regex = /^[\u4e00-\u9fa5,。!?,!?]+$/;
 
@@ -1626,7 +1695,7 @@ export default {
         confirmButtonText: "确定",
         cancelButtonText: "取消",
         type: "warning"
-      }).then(function() {
+      }).then(function () {
         return delSop(ids);
       }).then(() => {
         this.getList();
@@ -1634,6 +1703,17 @@ export default {
       }).catch(() => {
       });
     },
+    /** 删除按钮操作 */
+    handleSopLogsDelete() {
+      this.$confirm('是否确认删除企微sop编号为"' + this.ids + '"的所有发送任务(执行记录)数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => delSopLogs(this.ids)).then(() => {
+        this.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+    },
     /**
      * 批量执行SOP任务
      */
@@ -1706,32 +1786,32 @@ export default {
     /**
      * 一键群发
      */
-    handleSendMsg(row) {
+    handleSendMsg(row){
       this.sendMsgOpen.open = true;
       this.sendMsgOpen.id = row.id;
       this.sendMsgOpen.row = row;
       this.msgForm.chatIds = row.chatId.split(",");
     },
-    submitMsgForm() {
+    submitMsgForm(){
       this.$refs["msgForm"].validate(valid => {
         if (valid) {
-          this.msgForm.setting = JSON.stringify(this.setting)
-          this.msgForm.sopId = this.sendMsgOpen.row.id;
-          this.msgForm.corpId = this.sendMsgOpen.row.corpId;
-          this.msgForm.filterMode = this.sendMsgOpen.row.filterMode;
+          this.msgForm.setting=JSON.stringify(this.setting)
+          this.msgForm.sopId=this.sendMsgOpen.row.id;
+          this.msgForm.corpId=this.sendMsgOpen.row.corpId;
+          this.msgForm.filterMode=this.sendMsgOpen.row.filterMode;
 
           if (this.setting.length <= 0) {
             return this.$message.error("请添加规则")
           }
-          if (this.msgForm.courseId === null || this.msgForm.courseId === '') {
+          if (this.msgForm.courseId===null || this.msgForm.courseId===''){
             return this.$message.error("课程不能为空")
           }
 
-          if (this.msgForm.videoId === null || this.msgForm.videoId === '') {
+          if (this.msgForm.videoId===null || this.msgForm.videoId===''){
             return this.$message.error("课节不能为空")
           }
 
-          if (this.msgForm.courseType === null || this.msgForm.courseType === '') {
+          if (this.msgForm.courseType===null || this.msgForm.courseType===''){
             return this.$message.error("消息类型不能为空")
           }
 
@@ -1777,19 +1857,19 @@ export default {
           sendMsgSop(this.msgForm).then(response => {
             this.msgSuccess("一键群发成功");
             loading.close();
-            this.setting = [];
+            this.setting=[];
             this.msgForm = {
-              videoId: null,
-              courseId: null,
-              courseType: null,
-              setting: null,
-              isRegister: 2,
-              sendType: 1,
-              filterMode: 2,
+              videoId:null,
+              courseId:null,
+              courseType:null,
+              setting:null,
+              isRegister:2,
+              sendType:1,
+              filterMode:2,
 
             }
             this.getList();
-          }).finally(() => {
+          }).finally(()=>{
             loading.close();
           });
 
@@ -1805,6 +1885,9 @@ export default {
             this.$set(this.setting[i], 'linkTitle', selectedCourse.dictLabel);
             this.$set(this.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
           }
+          if (selectedCourse && this.setting[i].contentType == 4 && this.msgForm.courseId != null) {
+            this.$set(this.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+          }
 
         }
 
@@ -1813,12 +1896,12 @@ export default {
         this.videoList = response.list;
       });
     },
-    cancelMsgForm() {
+    cancelMsgForm(){
       this.sendMsgOpen.open = false;
       this.resetSendMsgSop();
     },
-    delSetList(index) {
-      this.setting.splice(index, 1)
+    delSetList(index){
+      this.setting.splice(index,1)
     },
   }
 };

+ 110 - 28
src/views/qw/sop/mySop.vue

@@ -104,6 +104,18 @@
         >删除
         </el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleSopLogsDelete"
+          v-hasPermi="['qw:sopLogs:removeAll']"
+        >批量删除执行任务
+        </el-button>
+      </el-col>
       <el-col :span="1.5">
         <el-button
           type="success"
@@ -128,6 +140,18 @@
         >批量执行SOP
         </el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-tooltip class="item" effect="dark" content="此功能用于给 选中的 SOP任务营期 内【所有的】客户发送 消息【或者发送草稿-/-清楚草稿】" placement="top">
+          <el-button
+            type="warning"
+            icon="el-icon-s-promotion"
+            size="mini"
+            :disabled="multiple"
+            @click="handleCampSendMsg"
+            v-hasPermi="['qw:sopUserLogsInfo:msgSop']"
+          >SOP营期一键群发(或草稿)</el-button>
+        </el-tooltip>
+      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
     <el-table v-loading="loading" border :data="sopList" @selection-change="handleSelectionChange">
@@ -313,6 +337,7 @@
       @pagination="getList"
     />
 
+    <send-msg-sop-open-tool ref="sendMsgSopOpenTool" ></send-msg-sop-open-tool>
     <!-- 添加或修改企微sop对话框 -->
     <el-dialog :title="title" :visible.sync="open" width="1000px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="100px">
@@ -734,7 +759,20 @@
                       </el-card>
                     </div>
                     <div v-if="item.contentType == 4">
-
+                      <el-card class="box-card">
+                        <el-form-item label="标题" prop="miniprogramTitle">
+                          <el-input v-model="item.miniprogramTitle" placeholder="请输入小程序消息标题,最长为64字"  />
+                        </el-form-item>
+                        <el-form-item label="封面" prop="miniprogramPicUrl">
+                          <ImageUpload v-model="item.miniprogramPicUrl"  type="image" :num="10" :width="150" :height="150" />
+                        </el-form-item>
+                        <el-form-item label="appid" prop="miniprogramAppid" v-show="false" >
+                          <el-input v-model="item.miniprogramAppid" disabled />
+                        </el-form-item>
+                        <el-form-item label="page路径" prop="miniprogramPage" v-show="false" label-width="100px" style="margin-left: -30px" >
+                          <el-input v-model="item.miniprogramPage" placeholder="小程序消息打开后的路径"  disabled />
+                        </el-form-item>
+                      </el-card>
                     </div>
                     <div v-if="item.contentType == 5 ">
 
@@ -853,7 +891,7 @@
 import {
   addSop,
   courseList,
-  delSop,
+  delSop, delSopLogs,
   exportSop,
   getSopVoiceList, listMySop,
   listSop,
@@ -865,7 +903,7 @@ import {
 } from '@/api/qw/sop'
 import {sendMsgSop} from "@/api/qw/sopUserLogsInfo";
 import {listSopTemp} from "@/api/qw/sopTemp";
-import {getQwAllUserList, listUser} from '@/api/company/companyUser'
+import {getQwAllUserList, getQwMyUserList, listUser} from '../../../api/company/companyUser'
 import qwUserList from '@/views/qw/user/qwUserList.vue'
 import ImageUpload from "@/views/qw/sop/ImageUpload";
 import CustomerGroupDetails from '@/views/qw/groupMsg/customerGroupDetails.vue'
@@ -873,10 +911,11 @@ import sopLogsDetails from '@/views/qw/sopLogs/sopLogsList.vue'
 import {listTag,} from "@/api/qw/tag";
 import {getMyQwCompanyList} from "@/api/qw/user";
 import {allList} from "@/api/qw/groupChat";
+import SendMsgSopOpenTool from '@/views/qw/sopUserLogsInfo/sendMsgSopOpenTool.vue'
 
 export default {
   name: "Sop",
-  components: {CustomerGroupDetails, qwUserList, ImageUpload, sopLogsDetails},
+  components: {CustomerGroupDetails, qwUserList, ImageUpload, sopLogsDetails, SendMsgSopOpenTool},
   data() {
     return {
       // 存储每一行的展开状态
@@ -1071,6 +1110,16 @@ export default {
     }
   },
   methods: {
+    /**
+     * SOP任务营期一键群发
+     */
+    handleCampSendMsg(){
+
+      setTimeout(() => {
+        this.$refs.sendMsgSopOpenTool.oneClickGroupSending(this.ids,2,this.queryParams.corpId);
+      }, 500);
+
+    },
     voice(id) {
       this.voiceForm.queryParams.id = id;
       getSopVoiceList(this.voiceForm.queryParams).then(res => {
@@ -1174,7 +1223,7 @@ export default {
 
     //刷新部分数据
     refreshData(row) {
-      getQwAllUserList(row).then(response => {
+      getQwMyUserList(row).then(response => {
         this.companyUserList = response.data;
       });
 
@@ -1342,9 +1391,9 @@ export default {
       });
     },
 
-    addSetList() {
+    addSetList(){
       const newSetting = {
-        contentType: '1',
+        contentType:'1',
         value: '',
       };
       // 将新设置项添加到 content.setting 数组中
@@ -1355,7 +1404,7 @@ export default {
     handleContentTypeChange() {
 
       //如果是链接的才上
-      if (this.msgForm.courseId != null) {
+      if (this.msgForm.courseId != null ) {
         const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === this.msgForm.courseId);
         for (let i = 0; i < this.setting.length; i++) {
           //响应式直接给链接的标题/封面上值
@@ -1363,6 +1412,9 @@ export default {
             this.$set(this.setting[i], 'linkTitle', selectedCourse.dictLabel);
             this.$set(this.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
           }
+          if (selectedCourse && this.setting[i].contentType == 4 && this.msgForm.courseId != null) {
+            this.$set(this.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+          }
 
         }
 
@@ -1373,12 +1425,20 @@ export default {
 
         for (let i = 0; i < this.setting.length; i++) {
           //响应式直接给链接的描述上值
-          if (selectedVideo && this.setting[i].contentType == 3 && this.msgForm.videoId != null) {
-            this.$set(this.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+          if (selectedVideo && this.msgForm.videoId != null) {
+            console.log(2, this.setting[i].contentType)
+            if (this.setting[i].contentType == 3 || this.setting[i].contentType == 9) {
+              this.$set(this.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+            }
+            if (this.setting[i].contentType == 4) {
+              this.$set(this.setting[i], 'miniprogramTitle', selectedVideo.dictLabel);
+            }
+
           }
         }
       }
 
+
     },
     videoIdChange() {
       if (this.msgForm.videoId != null) {
@@ -1387,8 +1447,16 @@ export default {
 
         for (let i = 0; i < this.setting.length; i++) {
           //响应式直接给链接的描述上值
-          if (selectedVideo && this.setting[i].contentType == 3 && this.msgForm.videoId != null) {
-            this.$set(this.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+          if (selectedVideo && this.msgForm.videoId != null) {
+            if (this.setting[i].contentType == 3 || this.setting[i].contentType == 9) {
+              this.$set(this.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+            }
+
+            if (this.setting[i].contentType == 4) {
+              this.$set(this.setting[i], 'miniprogramTitle', selectedVideo.dictLabel);
+            }
+
+
           }
         }
       }
@@ -1420,7 +1488,7 @@ export default {
         createBy: null,
         createTime: null,
         isAutoSop: null,
-        autoSopTime: { autoSopType: 2, autoStartTime: '00:00', autoEndTime: '24:00', autoSopSend: 2 },
+        autoSopTime: {autoSopType: 2, autoStartTime: '00:00', autoEndTime: '24:00', autoSopSend: 2},
       };
       this.resetForm("form");
       this.tags = null;
@@ -1448,7 +1516,7 @@ export default {
     /** 新增按钮操作 */
     handleAdd() {
       this.reset();
-      this.$router.push('/qw/sop/addSop/' + this.queryParams.corpId)
+      this.$router.push('/qw/sop/addSop/' + this.queryParams.corpId+"/"+2)
       // this.open = true;
       // this.setting=[]
       // this.userSelectList=[]
@@ -1496,7 +1564,7 @@ export default {
         this.msgError(res.msg);
       }
     },
-    beforeAvatarUploadFile(file) {
+    beforeAvatarUploadFile(file){
       const isLt1M = file.size / 1024 / 1024 < 10;
       if (!isLt1M) {
         this.$message.error('上传大小不能超过 10MB!');
@@ -1510,7 +1578,7 @@ export default {
     },
 
     handleAvatarSuccessVideo(res, file, item) {
-      if (res.code == 200) {
+      if(res.code==200){
         // 使用 $set 确保响应式更新
         this.$set(item, 'videoUrl', res.url);
       } else {
@@ -1518,7 +1586,7 @@ export default {
       }
     },
 
-    beforeAvatarUploadVideo(file) {
+    beforeAvatarUploadVideo(file){
       const isLt30M = file.size / 1024 / 1024 < 10;
       const isMP4 = file.type === 'video/mp4';
 
@@ -1535,7 +1603,7 @@ export default {
       return true;
     },
 
-    handleInputVideoText(value, content) {
+    handleInputVideoText(value,content){
       // 允许的字符:中文、英文(大小写)、数字和指定标点符号(,。!?)
       const regex = /^[\u4e00-\u9fa5,。!?,!?]+$/;
 
@@ -1626,7 +1694,7 @@ export default {
         confirmButtonText: "确定",
         cancelButtonText: "取消",
         type: "warning"
-      }).then(function() {
+      }).then(function () {
         return delSop(ids);
       }).then(() => {
         this.getList();
@@ -1634,6 +1702,17 @@ export default {
       }).catch(() => {
       });
     },
+    /** 删除按钮操作 */
+    handleSopLogsDelete() {
+      this.$confirm('是否确认删除企微sop编号为"' + this.ids + '"的所有发送任务(执行记录)数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => delSopLogs(this.ids)).then(() => {
+        this.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+    },
     /**
      * 批量执行SOP任务
      */
@@ -1706,32 +1785,32 @@ export default {
     /**
      * 一键群发
      */
-    handleSendMsg(row) {
+    handleSendMsg(row){
       this.sendMsgOpen.open = true;
       this.sendMsgOpen.id = row.id;
       this.sendMsgOpen.row = row;
       this.msgForm.chatIds = row.chatId.split(",");
     },
-    submitMsgForm() {
+    submitMsgForm(){
       this.$refs["msgForm"].validate(valid => {
         if (valid) {
-          this.msgForm.setting = JSON.stringify(this.setting)
-          this.msgForm.sopId = this.sendMsgOpen.row.id;
-          this.msgForm.corpId = this.sendMsgOpen.row.corpId;
-          this.msgForm.filterMode = this.sendMsgOpen.row.filterMode;
+          this.msgForm.setting=JSON.stringify(this.setting)
+          this.msgForm.sopId=this.sendMsgOpen.row.id;
+          this.msgForm.corpId=this.sendMsgOpen.row.corpId;
+          this.msgForm.filterMode=this.sendMsgOpen.row.filterMode;
 
           if (this.setting.length <= 0) {
             return this.$message.error("请添加规则")
           }
-          if (this.msgForm.courseId === null || this.msgForm.courseId === '') {
+          if (this.msgForm.courseId===null || this.msgForm.courseId===''){
             return this.$message.error("课程不能为空")
           }
 
-          if (this.msgForm.videoId === null || this.msgForm.videoId === '') {
+          if (this.msgForm.videoId===null || this.msgForm.videoId===''){
             return this.$message.error("课节不能为空")
           }
 
-          if (this.msgForm.courseType === null || this.msgForm.courseType === '') {
+          if (this.msgForm.courseType===null || this.msgForm.courseType===''){
             return this.$message.error("消息类型不能为空")
           }
 
@@ -1805,6 +1884,9 @@ export default {
             this.$set(this.setting[i], 'linkTitle', selectedCourse.dictLabel);
             this.$set(this.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
           }
+          if (selectedCourse && this.setting[i].contentType == 4 && this.msgForm.courseId != null) {
+            this.$set(this.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+          }
 
         }
 

+ 2 - 1
src/views/qw/sop/sop.vue

@@ -1111,6 +1111,7 @@ export default {
     }
   },
   methods: {
+
     /**
      * SOP任务营期一键群发
      */
@@ -1518,7 +1519,7 @@ export default {
     /** 新增按钮操作 */
     handleAdd() {
       this.reset();
-      this.$router.push('/qw/sop/addSop/' + this.queryParams.corpId)
+      this.$router.push('/qw/sop/addSop/' + this.queryParams.corpId+"/"+1)
       // this.open = true;
       // this.setting=[]
       // this.userSelectList=[]

+ 95 - 28
src/views/qw/sop/updateSop.vue

@@ -3,11 +3,11 @@
     <div style="margin: 30px;" v-if="handleType==1"> 修改sop任务</div>
     <div style="margin: 30px;" v-if="handleType==2"> 查看sop任务
       <div style="float: right">
-<!--        <el-button-->
-<!--          type="primary"-->
-<!--          @click="handleUpdateTags()"-->
-<!--          v-hasPermi="['qw:sop:remove']"-->
-<!--        >修改标签</el-button>-->
+        <!--        <el-button-->
+        <!--          type="primary"-->
+        <!--          @click="handleUpdateTags()"-->
+        <!--          v-hasPermi="['qw:sop:remove']"-->
+        <!--        >修改标签</el-button>-->
         <el-button
           type="primary"
           @click="handleUpdateQwUser()"
@@ -37,16 +37,16 @@
           <el-input v-model="form.name" placeholder="请输入规则名称" />
         </el-form-item>
         <el-form-item label="状态" prop="status">
-            <dict-tag :options="statusOptions" :value="form.status"></dict-tag>
+          <dict-tag :options="statusOptions" :value="form.status"></dict-tag>
         </el-form-item>
-<!--        <el-form-item label="状态">-->
-<!--          <el-radio-group v-model="form.status">-->
-<!--            <el-radio-->
-<!--              v-for="dict in statusOptions"-->
-<!--              :label="dict.dictValue"-->
-<!--            >{{dict.dictLabel}}</el-radio>-->
-<!--          </el-radio-group>-->
-<!--        </el-form-item>-->
+        <!--        <el-form-item label="状态">-->
+        <!--          <el-radio-group v-model="form.status">-->
+        <!--            <el-radio-->
+        <!--              v-for="dict in statusOptions"-->
+        <!--              :label="dict.dictValue"-->
+        <!--            >{{dict.dictLabel}}</el-radio>-->
+        <!--          </el-radio-group>-->
+        <!--        </el-form-item>-->
         <el-form-item label="类别" prop="type">
           <el-radio-group v-model="form.type">
             <el-radio
@@ -103,6 +103,54 @@
               </el-tag>
             </div>
           </el-form-item>
+          <el-form-item label="是否自动创建群" prop="autoGroup" v-if="form.filterMode == 2">
+            <el-radio-group v-model="form.autoGroup">
+              <el-radio
+                :label="0"
+              >否
+              </el-radio>
+              <el-radio
+                :label="1"
+              >是
+              </el-radio>
+            </el-radio-group>
+            <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+              <i class="el-icon-info"></i>
+              选择的企业微信员工下面的群聊
+            </div>
+          </el-form-item>
+          <el-form-item label="是否注册用户" prop="autoGroup" v-if="form.filterMode == 2 && form.autoGroup == 1">
+            <el-radio-group v-model="form.autoUserReg">
+              <el-radio
+                :label="0"
+              >否
+              </el-radio>
+              <el-radio
+                :label="1"
+              >是
+              </el-radio>
+            </el-radio-group>
+            <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+              <i class="el-icon-info"></i>
+              筛选是否注册的用户
+            </div>
+          </el-form-item>
+          <el-form-item label="创建群聊评级" prop="chatIds" v-if="form.filterMode == 2 && form.autoGroup == 1">
+            <el-select multiple filterable clearable v-model="form.autoGroupLevelArray">
+              <el-option v-for="item in levelList" :key="item.value" :label="item.label" :value="item.value"/>
+            </el-select>
+            <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+              <i class="el-icon-info"></i>
+              选择需要拉取的客户评级
+            </div>
+          </el-form-item>
+          <el-form-item label="群聊名称" prop="chatIds" v-if="form.filterMode == 2 && form.autoGroup == 1">
+            <el-input v-model="form.groupName" maxlength="10" placeholder="请输入群聊名称" />
+            <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+              <i class="el-icon-info"></i>
+              创建群聊的名称(如有重复自动加数字区分)
+            </div>
+          </el-form-item>
           <el-form-item label="标签规则" prop="filterType">
             <el-radio-group v-model="form.filterType">
               <el-radio
@@ -268,14 +316,14 @@
             <!-- 如果 form.tempId 没有值,显示 '请选择模板' -->
             <span v-if="!form.tempId" style="margin: 3px; color: #999;">请选择模板</span>
           </div>
-<!--          <el-select v-model="form.tempId"  @focus="selectListSopTemp(form.sendType)" placeholder="请选择模板" v-loading="tempListLoading"   >-->
-<!--            <el-option-->
-<!--              v-for="dict in tempList"-->
-<!--              :label="dict.name"-->
-<!--              :value="dict.id">-->
-<!--            </el-option>-->
-<!--            <div v-if="tempListLoading" slot="prefix" class="select-prefix">正在查询相应模板...</div>-->
-<!--          </el-select>-->
+          <!--          <el-select v-model="form.tempId"  @focus="selectListSopTemp(form.sendType)" placeholder="请选择模板" v-loading="tempListLoading"   >-->
+          <!--            <el-option-->
+          <!--              v-for="dict in tempList"-->
+          <!--              :label="dict.name"-->
+          <!--              :value="dict.id">-->
+          <!--            </el-option>-->
+          <!--            <div v-if="tempListLoading" slot="prefix" class="select-prefix">正在查询相应模板...</div>-->
+          <!--          </el-select>-->
         </el-form-item>
 
         <el-form-item label="开启评论/弹幕" prop="openCommentStatus">
@@ -342,7 +390,7 @@ import ImageUpload from "@/views/qw/sop/ImageUpload";
 import CustomerGroupDetails from '@/views/qw/groupMsg/customerGroupDetails.vue'
 import sopLogsDetails from '@/views/qw/sopLogs/sopLogsList.vue'
 import { listTag, getTag, } from "@/api/qw/tag";
-import { getMyQwUserList,getMyQwCompanyList } from "@/api/qw/user";
+import {getMyQwUserList, getMyQwCompanyList, getQwUserByIds} from "@/api/qw/user";
 import {sopListWxUserGroup} from "@/api/wxUser/wxUserGroup";
 import source from "echarts/src/data/Source";
 import SopTemp from "@/views/qw/sopTemp/sopTemp.vue";
@@ -353,6 +401,14 @@ export default {
   components: {Tip, SopTemp, CustomerGroupDetails, qwUserList,ImageUpload,sopLogsDetails},
   data() {
     return {
+      levelList: [
+        {value: 1, label: "A"},
+        {value: 2, label: "B"},
+        {value: 3, label: "C"},
+        {value: 4, label: "D"},
+        {value: 5, label: "E"},
+        {value: -1, label: "未评级"},
+      ],
       updateQwUserDialog:{
         title:"修改成员",
         open:false
@@ -410,6 +466,7 @@ export default {
         status: 1,
         sendType:2,
         type: 2,
+        autoGroupLevelArray:[],
         filterType:2,
         autoSopTime:{},
       },
@@ -473,11 +530,11 @@ export default {
         '<span style="color: red;">(如有删除员工,对应的【营期也会删除】且【不可恢复】,请谨慎操作)</span>',
         "警告",
         {
-        confirmButtonText: "确定",
-        cancelButtonText: "取消",
-        type: "warning",
-        dangerouslyUseHTMLString: true // 允许使用 HTML 字符串
-      }).then(() => {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning",
+          dangerouslyUseHTMLString: true // 允许使用 HTML 字符串
+        }).then(() => {
         this.updateQwUserDialog.open=false;
         return updateSopQwUser(data);
       }).then(response => {
@@ -588,6 +645,7 @@ export default {
     //删除员工
     handleClosegroupUser(id){
       // const index = this.userSelectList.findIndex(t => t === list);
+      console.log(id)
       // if (index !== -1) {
       //   this.userSelectList.splice(index, 1);
       // }
@@ -644,6 +702,14 @@ export default {
         if (this.form.setting!=null){
           this.setting=JSON.parse(this.form.setting);
         }
+        if(this.form.autoGroupLevel != null){
+          this.form.autoGroupLevelArray = this.form.autoGroupLevel.split(",").map(Number);
+        }
+        if(this.form.qwUserIds != null){
+          getQwUserByIds(this.form.qwUserIds).then(res => {
+            this.companyQwUserList = res.data;
+          })
+        }
 
         listSopTemp({id:this.form.tempId}).then(response => {
           this.tempList = response.rows;
@@ -666,6 +732,7 @@ export default {
         type: 2,
         filterType:2,
         qwUserIds: null,
+        autoGroupLevelArray:[],
         corpId: null,
         setting: null,
         createBy: null,

+ 932 - 0
src/views/qw/sopTemp/deptIndex.vue

@@ -0,0 +1,932 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="模板标题" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          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="dict in statusOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="模板类型" prop="sendType">
+        <el-select v-model="queryParams.sendType" placeholder="请选择类型" clearable size="small">
+          <el-option
+            v-for="dict in sysQwSopType"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="排序" prop="sort">
+        <el-input
+          v-model="queryParams.sort"
+          placeholder="请输入排序"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="所属销售" prop="createBy">
+        <el-select v-model="queryParams.createBy" clearable filterable remote
+                   placeholder="请输入关键词" :remote-method="loadCompanyUserOptions"
+                   v-select-load-more="loadMoreCompanyUserOptions"
+                   :loading="companyUserOptionsLoading"
+                   @change="handleCompanyUserChange"
+                   @visible-change="handleCompanyUserDropdownVisible">
+          <el-option
+            v-for="item in companyUserOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-dropdown
+          v-hasPermi="['qw:sopTemp:deptAdd']"
+          @command="handleCommand"
+          trigger="click"
+          placement="bottom-start"
+        >
+          <el-dropdown-menu slot="dropdown" style="width: 120px;">
+            <el-dropdown-item
+              v-for="option in sysQwSopType"
+              :key="option.dictValue"
+              :command="option.dictValue"
+            >
+              <i :class="option.iconClass" style="margin-right: 10px;"></i>
+              {{ option.dictLabel }}
+            </el-dropdown-item>
+          </el-dropdown-menu>
+          <span class="el-dropdown-link">
+              <el-button type="primary" icon="el-icon-plus" plain size="mini">
+                新增模板
+              </el-button>
+            </span>
+        </el-dropdown>
+      </el-col>
+
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['qw:sopTemp:deptRemove']"
+        >删除
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['qw:sopTemp:deptExport']"
+        >导出
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" border :data="sopTempList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center"/>
+      <el-table-column label="模板编号" align="center" prop="id"/>
+      <el-table-column label="公司名称" align="center">
+        <template slot-scope="scope">
+          <el-tag v-for="item in companys" v-if="scope.row.companyId == item.companyId">{{item.companyName}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="模板标题" align="center" prop="name"/>
+      <el-table-column label="模板类型" align="center" prop="sendType">
+        <template slot-scope="scope">
+          <dict-tag :options="sysQwSopType" :value="scope.row.sendType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="间隔天数" align="center" prop="gap"/>
+      <el-table-column label="状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <dict-tag :options="statusOptions" :value="scope.row.status"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime"/>
+      <el-table-column label="修改时间" align="center" prop="updateTime"/>
+      <el-table-column label="排序" align="center" prop="sort"/>
+
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-connection"
+
+            @click="copyTemplate(scope.row)"
+            v-hasPermi="['qw:sopTemp:deptEdit']"
+          >复制模板
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-share"
+            @click="shareTemplate(scope.row)"
+            v-hasPermi="['qw:sopTemp:deptShare']"
+          >分享模板</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-s-promotion"
+            @click="handleQueryDetails(scope.row)"
+            v-hasPermi="['qw:sopTemp:deptList']"
+          >详情
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['qw:sopTemp:deptEdit']"
+          >修改
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate2(scope.row)"
+            v-hasPermi="['qw:sopTemp:deptEdit']"
+          >管理规则
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdateRed(scope.row)"
+            v-if="scope.row.sendType == 5"
+            v-hasPermi="['qw:sopTemp:deptEdit']"
+          >红包设置
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['qw:sopTemp:deptRemove']"
+          >删除
+          </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="80px">
+        <el-form-item label="名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入模板标题"/>
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-radio-group v-model="form.status">
+            <el-radio
+              v-for="dict in statusOptions"
+              :key="dict.dictValue"
+              :label="dict.dictValue"
+            >{{ dict.dictLabel }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="所属项目" prop="project" v-if="form.sendType != 5">
+          <el-select v-model="form.project" placeholder="请选择项目" filterable clearable size="small">
+            <el-option
+              v-for="dict in projectOptions"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="归属部门" prop="createByDept">
+          <treeselect
+            style="width: 220px"
+            :clearable="false"
+            v-model="form.createByDept"
+            :options="deptOptions"
+            clearable
+            :show-count="true"
+            placeholder="请选择归属部门"
+          />
+        </el-form-item>
+        <el-form-item label="课程" prop="courseId" v-if="form.sendType == 11 && !form.id">
+          <el-select v-model="form.courseId"placeholder="请选择课程" style=" margin-right: 10px;" size="mini" filterable>
+            <el-option
+              v-for="dict in courseList"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="parseInt(dict.dictValue)"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="间隔天数" prop="gap">
+          <el-input-number v-model="form.gap" :min="1" label="间隔天数"></el-input-number>
+        </el-form-item>
+        <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="是否开启官方群发" v-if="form.sendType == 11 && (form.id === null || form.id === undefined)">
+          <el-radio-group v-model="form.openOfficial">
+            <el-radio
+              v-for="dict in openOfficialOptions"
+              :key="dict.dictValue"
+              :label="dict.dictValue"
+            >{{ dict.dictLabel }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="内容" prop="modeContent">
+          <el-input v-model="form.modeContent" placeholder="请输入文字内容"  type="textarea" :rows="3"/>
+        </el-form-item>
+        <el-form-item label="发课时间" prop="time" v-if="form.sendType == 11 && !form.id">
+          <el-time-picker
+            class="custom-input"
+            v-model="form.time"
+            value-format="HH:mm"
+            format="HH:mm"
+            placeholder="时间"
+            style="width: 200px;height: 20px;margin-left: 10px;margin-top: 10px">
+          </el-time-picker>
+        </el-form-item>
+        <el-form-item label="每天催课次数" prop="num" v-if="form.sendType == 11 && !form.id">
+          <el-input-number v-model="form.num" :min="0" label="每天催课次数" @change="sendNumChange"></el-input-number>
+        </el-form-item>
+        <el-form-item label="催课时间" v-if="form.sendType == 11 && !form.id && form.num > 0">
+          <div v-for="(item, index) in form.timeList" :key="index" style="margin-bottom: 10px;">
+            <el-time-picker
+              class="custom-input"
+              v-model="item.value"
+              value-format="HH:mm"
+              format="HH:mm"
+              :picker-options="{ selectableRange: startTimeRange }"
+              placeholder="时间"
+              style="width: 150px; height: 20px; margin-left: 10px; margin-top: 10px">
+            </el-time-picker>
+
+            <el-input
+              v-model="item.desc"
+              placeholder="催课内容"
+              type="textarea" :rows="2"
+              style="width: 500px; margin-left: 10px; margin-top: 10px;">
+            </el-input>
+          </div>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer" style="display: flex;justify-content: flex-end;">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog title="课程红包" :visible.sync="redData.open" width="900px" append-to-body>
+      <el-table v-loading="redData.loading" border :data="redData.list" height="500px">
+        <el-table-column label="小节" align="left" prop="videoName"/>
+        <el-table-column label="金额" align="center">
+          <template slot-scope="scope">
+            <el-input-number v-model="scope.row.redPacketMoney" :min="0.01" step="0.01" label="红包金额"></el-input-number>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div slot="footer" class="dialog-footer" style="display: flex;justify-content: flex-end;">
+        <el-button type="primary" @click="updateRedData" :disabled="redData.loading">保 存</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="shareOptions.title" :visible.sync="shareOptions.open" width="800px" append-to-body>
+
+      <el-form :model="queryCompanyParams" ref="queryCompanyForm" :inline="true" v-show="showSearch" label-width="68px">
+        <el-form-item label="公司名称" prop="companyName">
+          <el-input
+            style="width: 220px"
+            v-model="queryCompanyParams.companyName"
+            placeholder="请输入企业名称"
+            clearable
+            size="small"
+            @keyup.enter.native="handleCompanyQuery"
+          />
+        </el-form-item>
+
+        <el-form-item label="状态" prop="status">
+          <el-select style="width: 220px" v-model="queryCompanyParams.status" placeholder="请选择状态" clearable size="small">
+            <el-option
+              v-for="dict in statusOptions"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleCompanyQuery">搜索</el-button>
+          <el-button icon="el-icon-refresh" size="mini" @click="resetCompanyQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+      <el-row :gutter="10" class="mb8">
+        <el-col :span="1.5">
+          <el-button
+            plain
+            type="primary"
+            icon="el-icon-connection"
+            size="mini"
+            :disabled="multiple"
+            @click="handleShareTemplate"
+            v-hasPermi="['qw:sopTemp:myShare']"
+          >分享模板</el-button>
+        </el-col>
+      </el-row>
+      <el-table v-loading="companysloading" border :data="companyList" @selection-change="handleSelectionCompany">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="公司编号" align="center" prop="companyId" />
+                <el-table-column label="企业名" align="center" prop="companyName" />
+        <!--        <el-table-column label="备注" align="center" prop="remark"/>-->
+        <el-table-column label="状态" align="center" prop="status" >
+          <template slot-scope="scope">
+            <el-tag prop="status" v-for="(item, index) in statusOptions"  :type="scope.row.status==1?'success':'danger'"  v-if="scope.row.status==item.dictValue">{{item.dictLabel}}</el-tag>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-show="companyTotal>0"
+        :total="companyTotal"
+        :page.sync="queryCompanyParams.pageNum"
+        :limit.sync="queryCompanyParams.pageSize"
+        @pagination="getCompanyList"
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  addTemp,
+  copyTemplate,
+  delSopTemp,
+  exportSopTemp,
+  getSopTemp,
+  listSopTemp,
+  getSelectableRange,
+  redList,
+  shareSopTemp,
+  updateRedPackage,
+  updateTemp, listSopTempDeptList
+} from "../../../api/qw/sopTemp";
+import { getCompanyList, listCompany } from '@/api/company/company'
+import {courseList, getRoles} from "@/api/qw/sop";
+import {treeselect} from "../../../api/company/companyDept";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import {getCompanyUserListLikeNameByDept} from "../../../api/company/companyUser";
+export default {
+  name: "SopTemp",
+  components: {Treeselect},
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      companysloading: false,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      courseList: [],
+      roles: [],
+      //选中的公司
+      companys: [],
+      deptOptions: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      companyTotal: 0,
+      total: 0,
+      sendType: 0,
+      // sop模板表格数据
+      sopTempList: [],
+      sysQwSopType: [],
+      companyList: [],
+      projectOptions: [],
+      startTimeRange: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      command: 0,
+      // 状态字典
+      statusOptions: [],
+      openOfficialOptions: [],
+
+      shareOptions: {
+        title: '分享模板',
+        open: false,
+        templateId: null,
+      },
+      redData: {
+        open: false,
+        loading: true,
+        list: [],
+      },
+      queryCompanyParams: {
+        pageNum: 1,
+        pageSize: 10,
+        companyName: null,
+        status: null,
+      },
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        setting: null,
+        status: '1',
+        sort: null,
+        companyId: null,
+        createBy: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        name: [
+          {required: true, message: '名称不能为空', trigger: 'blur'}
+        ],
+        status: [
+          {required: true, message: '状态不能为空', trigger: 'blur'}
+        ],
+        sort: [
+          {required: true, message: '排序不能为空', trigger: 'blur'}
+        ],
+        gap: [
+          {required: true, message: '间隔天数不能为空', trigger: 'blur'}
+        ],
+        time:[{
+          required: true, message: '发课时间不能为空', trigger: 'blur'
+        }],
+        courseId:[{
+          required: true, message: '课程不能为空', trigger: 'blur'
+        }],
+        companyId:[{
+          required: true, message: '销售公司不能为空', trigger: 'blur'
+        }],
+        project:[{
+          required: true, message: '所属项目不能为空', trigger: 'blur'
+        }],
+        createByDept:[
+          { required: true, message: '归属部门不能为空', trigger: 'blur' }
+        ]
+      },
+      contentRules: {
+        time: [{required: true, message: '时间不能为空', trigger: 'blur'}],
+      },
+      // 员工选项列表
+      companyUserOptionsParams: {
+        name: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      companyUserOptionsByAll: [],
+      companyUserOptions: [],
+      companyUserOptionsLoading: false,
+      companyUserFirstLoad: true, // 首次加载标志
+      companyUserDropdownVisible: false, // 下拉框显示状态
+
+    };
+  },
+  created() {
+    this.getList();
+
+    this.getDeptTreeSelect();
+
+    getRoles().then(res => {
+      this.roles = res.data;
+    })
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+
+    this.getDicts("sys_company_or").then(response => {
+      this.openOfficialOptions = response.data;
+    });
+
+    getSelectableRange().then(e => {
+      this.startTimeRange = e.data;
+    })
+    this.getDicts("sys_course_project").then(response => {
+      this.projectOptions = response.data;
+    });
+
+    this.getDicts("sys_qw_sop_type").then(response => {
+      this.sysQwSopType = response.data;
+    });
+
+    courseList().then(response => {
+      this.courseList = response.list;
+    });
+
+    this.getCompanyList();
+
+    getCompanyList().then(response => {
+      this.companys = response.data;
+    });
+  },
+  methods: {
+
+    handleCompanyUserChange(value) {
+      // 当清空选择时,将空字符串转为 null
+      this.queryParams.createBy = value === '' ? null : value;
+
+    },
+
+    /**
+     * 处理所属销售下拉框显示状态变化
+     */
+    handleCompanyUserDropdownVisible(visible) {
+      this.companyUserDropdownVisible = visible;
+      if (visible && this.companyUserFirstLoad) {
+        // 首次展开下拉框时加载数据
+        this.companyUserFirstLoad = false;
+        this.loadCompanyUserOptions('');
+      } else if (visible && this.companyUserOptions.length === 0) {
+        // 下拉框显示但无数据时重新加载
+        this.loadCompanyUserOptions('');
+      }
+    },
+
+    /**
+     * 加载更多员工选项
+     */
+    loadMoreCompanyUserOptions() {
+      if (!this.companyUserOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.companyUserOptionsParams.pageNum += 1
+      this.getCompanyUserListLikeName()
+    },
+
+    /**
+     * 根据名称模糊查询用户列表
+     * @param query 参数
+     */
+    loadCompanyUserOptions(query) {
+      this.companyUserOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.companyUserOptionsParams.pageNum = 1
+      this.companyUserOptionsParams.name = query
+      this.companyUserOptionsLoading = true;
+      this.getCompanyUserListLikeName()
+    },
+
+    /**
+     * 获取员工列表
+     */
+    getCompanyUserListLikeName(isAll) {
+
+      if (isAll){
+        this.companyUserOptionsParams.pageSize = 200;
+        getCompanyUserListLikeNameByDept(this.companyUserOptionsParams).then(response => {
+          this.companyUserOptionsByAll = [...this.companyUserOptions, ...response.data.list]
+          this.companyUserOptionsParams.hasNextPage = response.data.hasNextPage
+          this.companyUserOptionsLoading = false;
+        });
+      }else {
+        this.companyUserOptionsParams.pageSize = 10;
+        getCompanyUserListLikeNameByDept(this.companyUserOptionsParams).then(response => {
+          this.companyUserOptions = [...this.companyUserOptions, ...response.data.list]
+          this.companyUserOptionsParams.hasNextPage = response.data.hasNextPage
+          this.companyUserOptionsLoading = false;
+        });
+      }
+
+    },
+
+    handleCompanyQuery() {
+      this.queryCompanyParams.pageNum = 1;
+    },
+    resetCompanyQuery() {
+      this.resetForm("queryCompanyForm");
+      this.handleCompanyQuery();
+    },
+
+    getDeptTreeSelect() {
+      treeselect().then((response) => {
+        this.deptOptions = response.data;
+      });
+    },
+
+    /** 查询企业列表 */
+    getCompanyList() {
+      this.companysloading = true;
+      listCompany(this.queryCompanyParams).then(response => {
+        this.companyList = response.rows;
+        this.companyTotal = response.total;
+        this.companysloading = false;
+      });
+    },
+    /** 查询sop模板列表 */
+    getList() {
+      this.loading = true;
+      listSopTempDeptList(this.queryParams).then(response => {
+        this.sopTempList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        gap: 1,
+        sendType: this.sendType,
+        sort: 0,
+        openOfficial: "1",
+        time: "",
+        num: 1,
+        timeList: [{value: "",desc:""}],
+        status: "1",
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+
+    handleCommand(command) {
+      // if (command==4) {
+      //   this.$router.push('/qw/sopTemp/addAiChatTemp')
+      // }else{
+      this.sendType = command;
+      this.title = "新增";
+      this.reset();
+      this.open = true;
+      // this.$router.push('/qw/sopTemp/addTemp/'+command)
+      // }
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    //销售公司多选
+    handleSelectionCompany(selection) {
+      this.companys = selection.map(item => item.companyId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    // handleAdd() {
+    //   this.$router.push('/qw/sopTemp/addSopTemp')
+    // },
+    /**
+     * 查看详情
+     */
+    handleQueryDetails(row) {
+      // if (row.sendType==4) {
+      //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/3`)
+      // }else{
+      this.$router.push(`/qw/sopTemp/updateSopTemp/${row.id}/3`)
+      // }
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      // if (row.sendType==4) {
+      //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/1`)
+      // }else{
+      this.getInfo(row.id, 1);
+      // this.$router.push(`/qw/sopTemp/updateTemp/${row.id}/1`)
+      // }
+
+    },
+    /** 修改按钮操作 */
+    handleUpdate2(row) {
+      // if (row.sendType==4) {
+      //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/1`)
+      // }else{
+      let url = `/qw/sopTemp/updateSopTemp/${row.id}/1`;
+      console.info(url)
+      this.$router.push(url)
+      // }
+    },
+    /** 修改按钮操作 */
+    handleUpdateRed(row) {
+      this.redData.open = true;
+      redList(row.id).then(e => {
+        this.redData.loading = false;
+        this.redData.list = e.data;
+      })
+    },
+    /** 修改按钮操作 */
+    updateRedData() {
+      this.redData.loading = true;
+      updateRedPackage({list: this.redData.list}).then(e => {
+        this.redData.open = false;
+        this.getList();
+      }).catch(() => {
+        this.redData.loading = false;
+      });
+    },
+
+    /** 分享模板 */
+    shareTemplate(row) {
+      this.shareOptions.open = true;
+      this.shareOptions.templateId = row.id;
+    },
+    /**
+     * 复制模板
+     */
+    copyTemplate(row) {
+      // if(row.sendType==4){
+      //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/2`)
+      // }else{
+      this.getInfo(row.id, 2);
+      // this.$router.push(`/qw/sopTemp/updateSopTemp/${row.id}/2`)
+      // }
+    },
+    getInfo(id, command) {
+      getSopTemp(id).then(response => {
+        this.command = command;
+        if (command == 2) {
+          this.title = "复制";
+        }
+        if (command == 1) {
+          this.title = "修改";
+        }
+        this.form = response.data;
+        this.open = true;
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      delete this.form.rules
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+
+          if (this.command != 2 && this.form.id == null && this.form.sendType == 11){
+
+            const hasEmptyFields = this.form.timeList.some(item => {
+              return !item.value || !item.desc;
+            });
+
+            if (hasEmptyFields) {
+              this.$message.error("请填写【催课时间】和【催课内容】!");
+              return; // 阻止提交
+            }
+
+          }
+
+          let f = JSON.parse(JSON.stringify(this.form));
+          if (f.timeList && f.timeList.length > 0) {
+            f.timeDesc = f.timeList.map(item => item.desc);
+            f.timeList = f.timeList.map(item => item.value);
+          }
+
+          console.log("f-----------",f)
+          const loading = this.$loading({
+            lock: true,
+            text: 'Loading',
+            spinner: 'el-icon-loading',
+            background: 'rgba(0, 0, 0, 0.7)'
+          });
+          if (this.command == 2) {
+            copyTemplate(f).then(response => {
+              this.msgSuccess("复制成功");
+              this.open = false;
+              loading.close();
+              this.getList();
+            });
+          } else {
+            if (f.id != null) {
+              updateTemp(f).then(response => {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                loading.close();
+                this.getList();
+              });
+            } else {
+              addTemp(f).then(response => {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                loading.close();
+                this.getList();
+              });
+            }
+          }
+        }
+      });
+    },
+
+    handleShareTemplate() {
+      const companyIds = this.companys;
+      let templateId = this.shareOptions.templateId;
+      this.$confirm("确定将模板分享给 公司编号为:" + companyIds + "的公司?", "提醒", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return shareSopTemp({companyIds: companyIds, templeId: templateId});
+      }).then(() => {
+        this.companys = [];
+        this.shareOptions.open = false;
+        this.getList();
+        this.msgSuccess("分享成功");
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除当前所选模板?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delSopTemp(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有sop模板数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportSopTemp(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+      });
+    },
+    sendNumChange(val, old) {
+      if (val > old) {
+        for (let i = 0; i < val - old; i++) {
+          this.form.timeList.push({value: "",desc:""});
+        }
+      } else {
+        let len = old - val;
+        this.form.timeList.splice(Math.max(this.form.timeList.length - len, 0), len);
+      }
+    }
+  }
+};
+</script>

+ 102 - 2
src/views/qw/sopTemp/index.vue

@@ -39,6 +39,21 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
+      <el-form-item label="所属销售" prop="createBy">
+        <el-select v-model="queryParams.createBy" clearable filterable remote
+                   placeholder="请输入关键词" :remote-method="loadCompanyUserOptions"
+                   v-select-load-more="loadMoreCompanyUserOptions"
+                   :loading="companyUserOptionsLoading"
+                   @change="handleCompanyUserChange"
+                   @visible-change="handleCompanyUserDropdownVisible">
+          <el-option
+            v-for="item in companyUserOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue">
+          </el-option>
+        </el-select>
+      </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -406,6 +421,7 @@ import {courseList, getRoles} from "@/api/qw/sop";
 import {treeselect} from "../../../api/company/companyDept";
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import {getCompanyUserListLikeName} from "../../../api/company/companyUser";
 export default {
   name: "SopTemp",
   components: {Treeselect},
@@ -472,7 +488,8 @@ export default {
         setting: null,
         status: '1',
         sort: null,
-        companyId: null
+        companyId: null,
+        createBy: null
       },
       // 表单参数
       form: {},
@@ -508,7 +525,19 @@ export default {
       },
       contentRules: {
         time: [{required: true, message: '时间不能为空', trigger: 'blur'}],
-      }
+      },
+      // 员工选项列表
+      companyUserOptionsParams: {
+        name: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      companyUserOptionsByAll: [],
+      companyUserOptions: [],
+      companyUserOptionsLoading: false,
+      companyUserFirstLoad: true, // 首次加载标志
+      companyUserDropdownVisible: false, // 下拉框显示状态
     };
   },
   created() {
@@ -550,6 +579,77 @@ export default {
   },
   methods: {
 
+    handleCompanyUserChange(value) {
+      // 当清空选择时,将空字符串转为 null
+      this.queryParams.createBy = value === '' ? null : value;
+
+    },
+
+    /**
+     * 处理所属销售下拉框显示状态变化
+     */
+    handleCompanyUserDropdownVisible(visible) {
+      this.companyUserDropdownVisible = visible;
+      if (visible && this.companyUserFirstLoad) {
+        // 首次展开下拉框时加载数据
+        this.companyUserFirstLoad = false;
+        this.loadCompanyUserOptions('');
+      } else if (visible && this.companyUserOptions.length === 0) {
+        // 下拉框显示但无数据时重新加载
+        this.loadCompanyUserOptions('');
+      }
+    },
+
+    /**
+     * 加载更多员工选项
+     */
+    loadMoreCompanyUserOptions() {
+      if (!this.companyUserOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.companyUserOptionsParams.pageNum += 1
+      this.getCompanyUserListLikeName()
+    },
+
+    /**
+     * 根据名称模糊查询用户列表
+     * @param query 参数
+     */
+    loadCompanyUserOptions(query) {
+      this.companyUserOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.companyUserOptionsParams.pageNum = 1
+      this.companyUserOptionsParams.name = query
+      this.companyUserOptionsLoading = true;
+      this.getCompanyUserListLikeName()
+    },
+
+    /**
+     * 获取员工列表
+     */
+    getCompanyUserListLikeName(isAll) {
+
+      if (isAll){
+        this.companyUserOptionsParams.pageSize = 200;
+        getCompanyUserListLikeName(this.companyUserOptionsParams).then(response => {
+          this.companyUserOptionsByAll = [...this.companyUserOptions, ...response.data.list]
+          this.companyUserOptionsParams.hasNextPage = response.data.hasNextPage
+          this.companyUserOptionsLoading = false;
+        });
+      }else {
+        this.companyUserOptionsParams.pageSize = 10;
+        getCompanyUserListLikeName(this.companyUserOptionsParams).then(response => {
+          this.companyUserOptions = [...this.companyUserOptions, ...response.data.list]
+          this.companyUserOptionsParams.hasNextPage = response.data.hasNextPage
+          this.companyUserOptionsLoading = false;
+        });
+      }
+
+    },
     handleCompanyQuery() {
       this.queryCompanyParams.pageNum = 1;
     },

+ 830 - 0
src/views/qw/sopTemp/myIndex.vue

@@ -0,0 +1,830 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="模板标题" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          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="dict in statusOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="模板类型" prop="sendType">
+        <el-select v-model="queryParams.sendType" placeholder="请选择类型" clearable size="small">
+          <el-option
+            v-for="dict in sysQwSopType"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="排序" prop="sort">
+        <el-input
+          v-model="queryParams.sort"
+          placeholder="请输入排序"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-dropdown
+          v-hasPermi="['qw:sopTemp:myAdd']"
+          @command="handleCommand"
+          trigger="click"
+          placement="bottom-start"
+        >
+          <el-dropdown-menu slot="dropdown" style="width: 120px;">
+            <el-dropdown-item
+              v-for="option in sysQwSopType"
+              :key="option.dictValue"
+              :command="option.dictValue"
+            >
+              <i :class="option.iconClass" style="margin-right: 10px;"></i>
+              {{ option.dictLabel }}
+            </el-dropdown-item>
+          </el-dropdown-menu>
+          <span class="el-dropdown-link">
+              <el-button type="primary" icon="el-icon-plus" plain size="mini">
+                新增模板
+              </el-button>
+            </span>
+        </el-dropdown>
+      </el-col>
+
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['qw:sopTemp:myRemove']"
+        >删除
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['qw:sopTemp:myExport']"
+        >导出
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getMyList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" border :data="sopTempList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center"/>
+      <el-table-column label="模板编号" align="center" prop="id"/>
+      <el-table-column label="公司名称" align="center">
+        <template slot-scope="scope">
+          <el-tag v-for="item in companys" v-if="scope.row.companyId == item.companyId">{{item.companyName}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="模板标题" align="center" prop="name"/>
+      <el-table-column label="模板类型" align="center" prop="sendType">
+        <template slot-scope="scope">
+          <dict-tag :options="sysQwSopType" :value="scope.row.sendType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="间隔天数" align="center" prop="gap"/>
+      <el-table-column label="状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <dict-tag :options="statusOptions" :value="scope.row.status"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime"/>
+      <el-table-column label="修改时间" align="center" prop="updateTime"/>
+      <el-table-column label="排序" align="center" prop="sort"/>
+
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-connection"
+
+            @click="copyTemplate(scope.row)"
+            v-hasPermi="['qw:sopTemp:myEdit']"
+          >复制模板
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-share"
+            @click="shareTemplate(scope.row)"
+            v-hasPermi="['qw:sopTemp:myShare']"
+          >分享模板</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-s-promotion"
+            @click="handleQueryDetails(scope.row)"
+            v-hasPermi="['qw:sopTemp:myList']"
+          >详情
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['qw:sopTemp:myEdit']"
+          >修改
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate2(scope.row)"
+            v-hasPermi="['qw:sopTemp:myEdit']"
+          >管理规则
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdateRed(scope.row)"
+            v-if="scope.row.sendType == 5"
+            v-hasPermi="['qw:sopTemp:myEdit']"
+          >红包设置
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['qw:sopTemp:myRemove']"
+          >删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getMyList"
+    />
+
+    <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入模板标题"/>
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-radio-group v-model="form.status">
+            <el-radio
+              v-for="dict in statusOptions"
+              :key="dict.dictValue"
+              :label="dict.dictValue"
+            >{{ dict.dictLabel }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="所属项目" prop="project" v-if="form.sendType != 5">
+          <el-select v-model="form.project" placeholder="请选择项目" filterable clearable size="small">
+            <el-option
+              v-for="dict in projectOptions"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="归属部门" prop="createByDept">
+          <treeselect
+            style="width: 220px"
+            :clearable="false"
+            v-model="form.createByDept"
+            :options="deptOptions"
+            clearable
+            :show-count="true"
+            placeholder="请选择归属部门"
+          />
+        </el-form-item>
+        <el-form-item label="课程" prop="courseId" v-if="form.sendType == 11 && !form.id">
+          <el-select v-model="form.courseId"placeholder="请选择课程" style=" margin-right: 10px;" size="mini" filterable>
+            <el-option
+              v-for="dict in courseList"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="parseInt(dict.dictValue)"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="间隔天数" prop="gap">
+          <el-input-number v-model="form.gap" :min="1" label="间隔天数"></el-input-number>
+        </el-form-item>
+        <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="是否开启官方群发" v-if="form.sendType == 11 && (form.id === null || form.id === undefined)">
+          <el-radio-group v-model="form.openOfficial">
+            <el-radio
+              v-for="dict in openOfficialOptions"
+              :key="dict.dictValue"
+              :label="dict.dictValue"
+            >{{ dict.dictLabel }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="内容" prop="modeContent">
+          <el-input v-model="form.modeContent" placeholder="请输入文字内容"  type="textarea" :rows="3"/>
+        </el-form-item>
+        <el-form-item label="发课时间" prop="time" v-if="form.sendType == 11 && !form.id">
+          <el-time-picker
+            class="custom-input"
+            v-model="form.time"
+            value-format="HH:mm"
+            format="HH:mm"
+            placeholder="时间"
+            style="width: 200px;height: 20px;margin-left: 10px;margin-top: 10px">
+          </el-time-picker>
+        </el-form-item>
+        <el-form-item label="每天催课次数" prop="num" v-if="form.sendType == 11 && !form.id">
+          <el-input-number v-model="form.num" :min="0" label="每天催课次数" @change="sendNumChange"></el-input-number>
+        </el-form-item>
+        <el-form-item label="催课时间" v-if="form.sendType == 11 && !form.id && form.num > 0">
+          <div v-for="(item, index) in form.timeList" :key="index" style="margin-bottom: 10px;">
+            <el-time-picker
+              class="custom-input"
+              v-model="item.value"
+              value-format="HH:mm"
+              format="HH:mm"
+              :picker-options="{ selectableRange: startTimeRange }"
+              placeholder="时间"
+              style="width: 150px; height: 20px; margin-left: 10px; margin-top: 10px">
+            </el-time-picker>
+
+            <el-input
+              v-model="item.desc"
+              placeholder="催课内容"
+              type="textarea" :rows="2"
+              style="width: 500px; margin-left: 10px; margin-top: 10px;">
+            </el-input>
+          </div>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer" style="display: flex;justify-content: flex-end;">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog title="课程红包" :visible.sync="redData.open" width="900px" append-to-body>
+      <el-table v-loading="redData.loading" border :data="redData.list" height="500px">
+        <el-table-column label="小节" align="left" prop="videoName"/>
+        <el-table-column label="金额" align="center">
+          <template slot-scope="scope">
+            <el-input-number v-model="scope.row.redPacketMoney" :min="0.01" step="0.01" label="红包金额"></el-input-number>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div slot="footer" class="dialog-footer" style="display: flex;justify-content: flex-end;">
+        <el-button type="primary" @click="updateRedData" :disabled="redData.loading">保 存</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="shareOptions.title" :visible.sync="shareOptions.open" width="800px" append-to-body>
+
+      <el-form :model="queryCompanyParams" ref="queryCompanyForm" :inline="true" v-show="showSearch" label-width="68px">
+        <el-form-item label="公司名称" prop="companyName">
+          <el-input
+            style="width: 220px"
+            v-model="queryCompanyParams.companyName"
+            placeholder="请输入企业名称"
+            clearable
+            size="small"
+            @keyup.enter.native="handleCompanyQuery"
+          />
+        </el-form-item>
+
+        <el-form-item label="状态" prop="status">
+          <el-select style="width: 220px" v-model="queryCompanyParams.status" placeholder="请选择状态" clearable size="small">
+            <el-option
+              v-for="dict in statusOptions"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleCompanyQuery">搜索</el-button>
+          <el-button icon="el-icon-refresh" size="mini" @click="resetCompanyQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+      <el-row :gutter="10" class="mb8">
+        <el-col :span="1.5">
+          <el-button
+            plain
+            type="primary"
+            icon="el-icon-connection"
+            size="mini"
+            :disabled="multiple"
+            @click="handleShareTemplate"
+            v-hasPermi="['qw:sopTemp:myShare']"
+          >分享模板</el-button>
+        </el-col>
+      </el-row>
+      <el-table v-loading="companysloading" border :data="companyList" @selection-change="handleSelectionCompany">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="公司编号" align="center" prop="companyId" />
+                <el-table-column label="企业名" align="center" prop="companyName" />
+        <!--        <el-table-column label="备注" align="center" prop="remark"/>-->
+        <el-table-column label="状态" align="center" prop="status" >
+          <template slot-scope="scope">
+            <el-tag prop="status" v-for="(item, index) in statusOptions"  :type="scope.row.status==1?'success':'danger'"  v-if="scope.row.status==item.dictValue">{{item.dictLabel}}</el-tag>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-show="companyTotal>0"
+        :total="companyTotal"
+        :page.sync="queryCompanyParams.pageNum"
+        :limit.sync="queryCompanyParams.pageSize"
+        @pagination="getCompanyList"
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  addTemp,
+  copyTemplate,
+  delSopTemp,
+  exportSopTemp,
+  getSopTemp,
+  listSopTemp,
+  getSelectableRange,
+  redList,
+  shareSopTemp,
+  updateRedPackage,
+  updateTemp, listSopTempMyList
+} from "../../../api/qw/sopTemp";
+import { getCompanyList, listCompany } from '@/api/company/company'
+import {courseList, getRoles} from "@/api/qw/sop";
+import {treeselect} from "../../../api/company/companyDept";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+export default {
+  name: "SopTemp",
+  components: {Treeselect},
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      companysloading: false,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      courseList: [],
+      roles: [],
+      //选中的公司
+      companys: [],
+      deptOptions: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      companyTotal: 0,
+      total: 0,
+      sendType: 0,
+      // sop模板表格数据
+      sopTempList: [],
+      sysQwSopType: [],
+      companyList: [],
+      projectOptions: [],
+      startTimeRange: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      command: 0,
+      // 状态字典
+      statusOptions: [],
+      openOfficialOptions: [],
+
+      shareOptions: {
+        title: '分享模板',
+        open: false,
+        templateId: null,
+      },
+      redData: {
+        open: false,
+        loading: true,
+        list: [],
+      },
+      queryCompanyParams: {
+        pageNum: 1,
+        pageSize: 10,
+        companyName: null,
+        status: null,
+      },
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        setting: null,
+        status: '1',
+        sort: null,
+        companyId: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        name: [
+          {required: true, message: '名称不能为空', trigger: 'blur'}
+        ],
+        status: [
+          {required: true, message: '状态不能为空', trigger: 'blur'}
+        ],
+        sort: [
+          {required: true, message: '排序不能为空', trigger: 'blur'}
+        ],
+        gap: [
+          {required: true, message: '间隔天数不能为空', trigger: 'blur'}
+        ],
+        time:[{
+          required: true, message: '发课时间不能为空', trigger: 'blur'
+        }],
+        courseId:[{
+          required: true, message: '课程不能为空', trigger: 'blur'
+        }],
+        companyId:[{
+          required: true, message: '销售公司不能为空', trigger: 'blur'
+        }],
+        project:[{
+          required: true, message: '所属项目不能为空', trigger: 'blur'
+        }],
+        createByDept:[
+          { required: true, message: '归属部门不能为空', trigger: 'blur' }
+        ]
+      },
+      contentRules: {
+        time: [{required: true, message: '时间不能为空', trigger: 'blur'}],
+      }
+    };
+  },
+  created() {
+    this.getMyList();
+
+    this.getDeptTreeSelect();
+
+    getRoles().then(res => {
+      this.roles = res.data;
+    })
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+
+    this.getDicts("sys_company_or").then(response => {
+      this.openOfficialOptions = response.data;
+    });
+
+    getSelectableRange().then(e => {
+      this.startTimeRange = e.data;
+    })
+    this.getDicts("sys_course_project").then(response => {
+      this.projectOptions = response.data;
+    });
+
+    this.getDicts("sys_qw_sop_type").then(response => {
+      this.sysQwSopType = response.data;
+    });
+
+    courseList().then(response => {
+      this.courseList = response.list;
+    });
+
+    this.getCompanyList();
+
+    getCompanyList().then(response => {
+      this.companys = response.data;
+    });
+  },
+  methods: {
+
+    handleCompanyQuery() {
+      this.queryCompanyParams.pageNum = 1;
+    },
+    resetCompanyQuery() {
+      this.resetForm("queryCompanyForm");
+      this.handleCompanyQuery();
+    },
+
+    getDeptTreeSelect() {
+      treeselect().then((response) => {
+        this.deptOptions = response.data;
+      });
+    },
+
+    /** 查询企业列表 */
+    getCompanyList() {
+      this.companysloading = true;
+      listCompany(this.queryCompanyParams).then(response => {
+        this.companyList = response.rows;
+        this.companyTotal = response.total;
+        this.companysloading = false;
+      });
+    },
+    /** 查询sop模板列表 */
+    getMyList() {
+      this.loading = true;
+      listSopTempMyList(this.queryParams).then(response => {
+        this.sopTempList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        gap: 1,
+        sendType: this.sendType,
+        sort: 0,
+        openOfficial: "1",
+        time: "",
+        num: 1,
+        timeList: [{value: "",desc:""}],
+        status: "1",
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getMyList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+
+    handleCommand(command) {
+      // if (command==4) {
+      //   this.$router.push('/qw/sopTemp/addAiChatTemp')
+      // }else{
+      this.sendType = command;
+      this.title = "新增";
+      this.reset();
+      this.open = true;
+      // this.$router.push('/qw/sopTemp/addTemp/'+command)
+      // }
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    //销售公司多选
+    handleSelectionCompany(selection) {
+      this.companys = selection.map(item => item.companyId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    // handleAdd() {
+    //   this.$router.push('/qw/sopTemp/addSopTemp')
+    // },
+    /**
+     * 查看详情
+     */
+    handleQueryDetails(row) {
+      // if (row.sendType==4) {
+      //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/3`)
+      // }else{
+      this.$router.push(`/qw/sopTemp/updateSopTemp/${row.id}/3`)
+      // }
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      // if (row.sendType==4) {
+      //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/1`)
+      // }else{
+      this.getInfo(row.id, 1);
+      // this.$router.push(`/qw/sopTemp/updateTemp/${row.id}/1`)
+      // }
+
+    },
+    /** 修改按钮操作 */
+    handleUpdate2(row) {
+      // if (row.sendType==4) {
+      //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/1`)
+      // }else{
+      let url = `/qw/sopTemp/updateSopTemp/${row.id}/1`;
+      console.info(url)
+      this.$router.push(url)
+      // }
+    },
+    /** 修改按钮操作 */
+    handleUpdateRed(row) {
+      this.redData.open = true;
+      redList(row.id).then(e => {
+        this.redData.loading = false;
+        this.redData.list = e.data;
+      })
+    },
+    /** 修改按钮操作 */
+    updateRedData() {
+      this.redData.loading = true;
+      updateRedPackage({list: this.redData.list}).then(e => {
+        this.redData.open = false;
+        this.getMyList();
+      }).catch(() => {
+        this.redData.loading = false;
+      });
+    },
+
+    /** 分享模板 */
+    shareTemplate(row) {
+      this.shareOptions.open = true;
+      this.shareOptions.templateId = row.id;
+    },
+    /**
+     * 复制模板
+     */
+    copyTemplate(row) {
+      // if(row.sendType==4){
+      //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/2`)
+      // }else{
+      this.getInfo(row.id, 2);
+      // this.$router.push(`/qw/sopTemp/updateSopTemp/${row.id}/2`)
+      // }
+    },
+    getInfo(id, command) {
+      getSopTemp(id).then(response => {
+        this.command = command;
+        if (command == 2) {
+          this.title = "复制";
+        }
+        if (command == 1) {
+          this.title = "修改";
+        }
+        this.form = response.data;
+        this.open = true;
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      delete this.form.rules
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+
+          if (this.command != 2 && this.form.id == null && this.form.sendType == 11){
+
+            const hasEmptyFields = this.form.timeList.some(item => {
+              return !item.value || !item.desc;
+            });
+
+            if (hasEmptyFields) {
+              this.$message.error("请填写【催课时间】和【催课内容】!");
+              return; // 阻止提交
+            }
+
+          }
+
+          let f = JSON.parse(JSON.stringify(this.form));
+          if (f.timeList && f.timeList.length > 0) {
+            f.timeDesc = f.timeList.map(item => item.desc);
+            f.timeList = f.timeList.map(item => item.value);
+          }
+
+          console.log("f-----------",f)
+          const loading = this.$loading({
+            lock: true,
+            text: 'Loading',
+            spinner: 'el-icon-loading',
+            background: 'rgba(0, 0, 0, 0.7)'
+          });
+          if (this.command == 2) {
+            copyTemplate(f).then(response => {
+              this.msgSuccess("复制成功");
+              this.open = false;
+              loading.close();
+              this.getMyList();
+            });
+          } else {
+            if (f.id != null) {
+              updateTemp(f).then(response => {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                loading.close();
+                this.getMyList();
+              });
+            } else {
+              addTemp(f).then(response => {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                loading.close();
+                this.getMyList();
+              });
+            }
+          }
+        }
+      });
+    },
+
+    handleShareTemplate() {
+      const companyIds = this.companys;
+      let templateId = this.shareOptions.templateId;
+      this.$confirm("确定将模板分享给 公司编号为:" + companyIds + "的公司?", "提醒", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return shareSopTemp({companyIds: companyIds, templeId: templateId});
+      }).then(() => {
+        this.companys = [];
+        this.shareOptions.open = false;
+        this.getMyList();
+        this.msgSuccess("分享成功");
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除当前所选模板?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delSopTemp(ids);
+      }).then(() => {
+        this.getMyListgetMyList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有sop模板数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportSopTemp(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+      });
+    },
+    sendNumChange(val, old) {
+      if (val > old) {
+        for (let i = 0; i < val - old; i++) {
+          this.form.timeList.push({value: "",desc:""});
+        }
+      } else {
+        let len = old - val;
+        this.form.timeList.splice(Math.max(this.form.timeList.length - len, 0), len);
+      }
+    }
+  }
+};
+</script>

+ 35 - 13
src/views/qw/sopTemp/sopTemp.vue

@@ -34,7 +34,7 @@
             plain
             icon="el-icon-edit"
             @click="handleSopTemp(scope.row)"
-            v-hasPermi="['qw:sopTemp:edit']"
+            v-hasPermi="['qw:sopTemp:edit', 'qw:sopTemp:myEdit', 'qw:sopTemp:deptEdit']"
           >选择此模板</el-button>
         </template>
       </el-table-column>
@@ -51,7 +51,7 @@
 </template>
 
 <script>
-import { listSopTemp } from "@/api/qw/sopTemp";
+import { listSopTemp,listSopTempDeptList,listSopTempMyList } from "../../../api/qw/sopTemp";
 
 export default {
   name: "SopTempComments",
@@ -90,6 +90,7 @@ export default {
         sendType:null,
         status:1,
       },
+      formType:null,
       // 表单参数
       form: {},
       // 表单校验
@@ -109,7 +110,10 @@ export default {
   },
   methods: {
     /** 查询sop模板列表 */
-    getList(sendType,isUpdate,cuoser) {
+    getList(sendType,isUpdate,cuoser,formType) {
+
+      console.log("formType",formType)
+
       if(cuoser == null){
         cuoser = false;
       }
@@ -119,20 +123,38 @@ export default {
       this.loading = true;
       this.queryParams.sendType= sendType;
       this.queryParams.cuoser= cuoser;
-      listSopTemp(this.queryParams).then(response => {
-        this.sopTempList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
+      this.formType= formType;
+
+      if (formType==2 || formType==3) {
+        listSopTempDeptList(this.queryParams).then(response => {
+          this.sopTempList = response.rows;
+          this.total = response.total;
+          this.loading = false;
+        });
+      } else {
+        listSopTemp(this.queryParams).then(response => {
+          this.sopTempList = response.rows;
+          this.total = response.total;
+          this.loading = false;
+        });
+      }
     },
 
     getResetList(){
       this.loading = true;
-      listSopTemp(this.queryParams).then(response => {
-        this.sopTempList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
+      if (this.formType==2 || this.formType==3) {
+        listSopTempMyList(this.queryParams).then(response => {
+          this.sopTempList = response.rows;
+          this.total = response.total;
+          this.loading = false;
+        });
+      } else {
+        listSopTemp(this.queryParams).then(response => {
+          this.sopTempList = response.rows;
+          this.total = response.total;
+          this.loading = false;
+        });
+      }
     },
     // 取消按钮
     cancel() {

+ 184 - 74
src/views/qw/sopTemp/updateSopTemp.vue

@@ -496,26 +496,26 @@
                                             </el-card>
                                           </div>
                                         </el-form-item>
-<!--                                        <el-form-item label="添加短链"-->
-<!--                                                      v-if="content.type == 2 && setList.contentType == 1  ">-->
-<!--                                          <el-tooltip content="请先根据课程选定课程小节之后再添加" effect="dark"-->
-<!--                                                      :disabled="!content.videoId">-->
-<!--                                            <el-switch-->
-<!--                                              @change="updateHtml"-->
-<!--                                              v-model="setList.isBindUrl"-->
-<!--                                              :disabled="!content.videoId && formType == 3 && !roles.includes('edit_sop_temp_content')"-->
-<!--                                              active-color="#13ce66"-->
-<!--                                              inactive-color="#DCDFE6"-->
-<!--                                              active-value="1"-->
-<!--                                              inactive-value="2">-->
-<!--                                            </el-switch>-->
-<!--                                          </el-tooltip>-->
-
-<!--                                          <span v-if="setList.isBindUrl == '1'"-->
-<!--                                                style="margin-left: 10px; color: #13ce66">添加URL</span>-->
-<!--                                          <span v-if="setList.isBindUrl == '2'"-->
-<!--                                                style="margin-left: 10px; color: #b1b4ba">不加URL</span>-->
-<!--                                        </el-form-item>-->
+                                        <!--                                        <el-form-item label="添加短链"-->
+                                        <!--                                                      v-if="content.type == 2 && setList.contentType == 1  ">-->
+                                        <!--                                          <el-tooltip content="请先根据课程选定课程小节之后再添加" effect="dark"-->
+                                        <!--                                                      :disabled="!content.videoId">-->
+                                        <!--                                            <el-switch-->
+                                        <!--                                              @change="updateHtml"-->
+                                        <!--                                              v-model="setList.isBindUrl"-->
+                                        <!--                                              :disabled="!content.videoId && formType == 3 && !roles.includes('edit_sop_temp_content')"-->
+                                        <!--                                              active-color="#13ce66"-->
+                                        <!--                                              inactive-color="#DCDFE6"-->
+                                        <!--                                              active-value="1"-->
+                                        <!--                                              inactive-value="2">-->
+                                        <!--                                            </el-switch>-->
+                                        <!--                                          </el-tooltip>-->
+
+                                        <!--                                          <span v-if="setList.isBindUrl == '1'"-->
+                                        <!--                                                style="margin-left: 10px; color: #13ce66">添加URL</span>-->
+                                        <!--                                          <span v-if="setList.isBindUrl == '2'"-->
+                                        <!--                                                style="margin-left: 10px; color: #b1b4ba">不加URL</span>-->
+                                        <!--                                        </el-form-item>-->
                                         <el-form-item label="课节过期时间"
                                                       v-if="content.type == 2 && (setList.isBindUrl == '1' || setList.contentType==4) && setList.contentType != 2  && setList.contentType != 5  && setList.contentType != 6 && setList.contentType != 8 && setList.contentType != 9 && setList.contentType != 10  ">
                                           <el-row>
@@ -543,25 +543,25 @@
                                           </el-input-number>
                                           <span class="tip">单位:分钟,最大1440分钟(24小时)</span>
                                         </el-form-item>
-<!--                                        <el-form-item label="内容" style="margin: 2%">-->
-<!--                                          <el-input-->
-<!--                                            v-model="setList.value"-->
-<!--                                            type="textarea"-->
-<!--                                            :rows="3"-->
-<!--                                            placeholder="内容"-->
-<!--                                            style="width: 90%;margin-top: 10px;"/>-->
-<!--                                        </el-form-item>-->
-<!--                                        <el-form-item label="交流状态" style="margin: 2%">-->
-<!--                                          <el-select v-model="setList.talkType" placeholder="更改交流状态" size="mini"-->
-<!--                                                     style=" margin-right: 10px;" clearable>-->
-<!--                                            <el-option label="非首次交流" value="非首次交流"></el-option>-->
-<!--                                            <el-option label="首次交流1" value="首次交流1"></el-option>-->
-<!--                                            <el-option label="首次交流2" value="首次交流2"></el-option>-->
-<!--                                            <el-option label="交流状态1" value="交流状态1"></el-option>-->
-<!--                                            <el-option label="交流状态2" value="交流状态2"></el-option>-->
-<!--                                            <el-option label="交流状态3" value="交流状态3"></el-option>-->
-<!--                                          </el-select>-->
-<!--                                        </el-form-item>-->
+                                        <!--                                        <el-form-item label="内容" style="margin: 2%">-->
+                                        <!--                                          <el-input-->
+                                        <!--                                            v-model="setList.value"-->
+                                        <!--                                            type="textarea"-->
+                                        <!--                                            :rows="3"-->
+                                        <!--                                            placeholder="内容"-->
+                                        <!--                                            style="width: 90%;margin-top: 10px;"/>-->
+                                        <!--                                        </el-form-item>-->
+                                        <!--                                        <el-form-item label="交流状态" style="margin: 2%">-->
+                                        <!--                                          <el-select v-model="setList.talkType" placeholder="更改交流状态" size="mini"-->
+                                        <!--                                                     style=" margin-right: 10px;" clearable>-->
+                                        <!--                                            <el-option label="非首次交流" value="非首次交流"></el-option>-->
+                                        <!--                                            <el-option label="首次交流1" value="首次交流1"></el-option>-->
+                                        <!--                                            <el-option label="首次交流2" value="首次交流2"></el-option>-->
+                                        <!--                                            <el-option label="交流状态1" value="交流状态1"></el-option>-->
+                                        <!--                                            <el-option label="交流状态2" value="交流状态2"></el-option>-->
+                                        <!--                                            <el-option label="交流状态3" value="交流状态3"></el-option>-->
+                                        <!--                                          </el-select>-->
+                                        <!--                                        </el-form-item>-->
                                       </el-form>
                                     </el-col>
                                     <el-col :span="1" :offset="1">
@@ -785,6 +785,12 @@ export default {
         companyId: null,
         gap: 1,
       },
+      // 缓存对象用于存储预加载的数据
+      tabCache: {},
+      // 标记哪些tab已经被请求过
+      loadedTabs: new Set(),
+      // 标记正在加载的tab,避免重复请求
+      loadingTabs: new Set(),
       // 表单校验
       rules: {
         name: [
@@ -975,48 +981,140 @@ export default {
       // });
     },
     // 切换标签保存数据
-    leave(index, oldIndex) {
+    async leave(index, oldIndex) {
       const newData = this.setting[index]
       if (newData.dayNum && newData.id) {
-        this.loading = true;
-        selectRulesInfo(newData.id).then(res => {
-          this.$nextTick(() => {
+        // 检查是否已经有缓存数据
+        if (this.tabCache[newData.id]) {
+          // 使用缓存数据,无需loading
+          this.applyCachedData(index, newData);
+        } else {
+          // 没有缓存,需要请求数据
+          this.loading = true;
+          try {
+            const res = await selectRulesInfo(newData.id);
+            // 缓存数据
+            this.tabCache[newData.id] = res.data;
+            this.loadedTabs.add(index);
+            this.applyDataToTab(index, newData, res.data);
+          } catch (error) {
+            console.error("获取规则信息失败:", error);
+          } finally {
             this.loading = false;
-            this.videoList = [];
-            this.addTag = [];
-            this.$set(this.setting, index, {
-              ...newData,
-              voice: res.data.voice,
-              content: res.data.list || [{type: this.defaultContentType, contentType: '1', setting: [{contentType: '1', value: ""}]}]
-            });
-
-            for (let j = 0; j < this.setting[index].content.length; j++) {
+          }
+        }
+        
+        // 预加载下一个tab的数据
+        this.preloadNextTab(index);
+      }
+    },
+    
+    // 应用缓存数据到tab
+    applyCachedData(index, newData) {
+      const cachedData = this.tabCache[newData.id];
+      this.$nextTick(() => {
+        this.videoList = [];
+        this.addTag = [];
+        this.$set(this.setting, index, {
+          ...newData,
+          voice: cachedData.voice,
+          content: cachedData.list || [{type: this.defaultContentType, contentType: '1', setting: [{contentType: '1', value: ""}]}]
+        });
 
-              if (this.setting[index].content[j].addTag != null) {
-                this.setting[index].content[j].addTag = JSON.parse(this.setting[index].content[j].addTag)
-              }
-              if (this.setting[index].content[j].delTag != null) {
-                this.setting[index].content[j].delTag = JSON.parse(this.setting[index].content[j].delTag)
-              }
-              this.videoList.push([])
-              this.addTag.push({
-                addTag: [],
-                inputVisible: false,
-                inputValue: '',
-                delTag: [],
-                delTagVisible: false,
-                delTagValue: ''
-              })
-              if (this.setting[index].content[j].type == 2) {
-                if (this.setting[index].content[j].hasOwnProperty('courseId')) {
-                  this.courseChange(this.setting[index].content[j], index, j);
-                }
-              }
-            }
+        for (let j = 0; j < this.setting[index].content.length; j++) {
+          if (this.setting[index].content[j].addTag != null) {
+            this.setting[index].content[j].addTag = JSON.parse(this.setting[index].content[j].addTag)
+          }
+          if (this.setting[index].content[j].delTag != null) {
+            this.setting[index].content[j].delTag = JSON.parse(this.setting[index].content[j].delTag)
+          }
+          this.videoList.push([])
+          this.addTag.push({
+            addTag: [],
+            inputVisible: false,
+            inputValue: '',
+            delTag: [],
+            delTagVisible: false,
+            delTagValue: ''
           })
+          if (this.setting[index].content[j].type == 2) {
+            if (this.setting[index].content[j].hasOwnProperty('courseId')) {
+              this.courseChange(this.setting[index].content[j], index, j);
+            }
+          }
+        }
+      });
+    },
+    
+    // 应用新获取的数据到tab
+    applyDataToTab(index, newData, data) {
+      this.$nextTick(() => {
+        this.videoList = [];
+        this.addTag = [];
+        this.$set(this.setting, index, {
+          ...newData,
+          voice: data.voice,
+          content: data.list || [{type: this.defaultContentType, contentType: '1', setting: [{contentType: '1', value: ""}]}]
         });
-      }
 
+        for (let j = 0; j < this.setting[index].content.length; j++) {
+          if (this.setting[index].content[j].addTag != null) {
+            this.setting[index].content[j].addTag = JSON.parse(this.setting[index].content[j].addTag)
+          }
+          if (this.setting[index].content[j].delTag != null) {
+            this.setting[index].content[j].delTag = JSON.parse(this.setting[index].content[j].delTag)
+          }
+          this.videoList.push([])
+          this.addTag.push({
+            addTag: [],
+            inputVisible: false,
+            inputValue: '',
+            delTag: [],
+            delTagVisible: false,
+            delTagValue: ''
+          })
+          if (this.setting[index].content[j].type == 2) {
+            if (this.setting[index].content[j].hasOwnProperty('courseId')) {
+              this.courseChange(this.setting[index].content[j], index, j);
+            }
+          }
+        }
+      });
+    },
+    
+    // 预加载下一个tab的数据
+    async preloadNextTab(currentIndex) {
+      const nextIndex = parseInt(currentIndex) + 1;
+      // 检查是否有下一个tab
+      if (nextIndex >= this.setting.length) {
+        return;
+      }
+      
+      const nextTabData = this.setting[nextIndex];
+      // 只预加载已存在的tab(有id的),跳过新建的tab
+      if (!nextTabData.id || !nextTabData.dayNum) {
+        return;
+      }
+      
+      // 如果已经加载过或正在加载,则跳过
+      if (this.loadedTabs.has(nextIndex) || this.loadingTabs.has(nextIndex) || this.tabCache[nextTabData.id]) {
+        return;
+      }
+      
+      // 标记为正在加载
+      this.loadingTabs.add(nextIndex);
+      
+      try {
+        const res = await selectRulesInfo(nextTabData.id);
+        // 缓存数据
+        this.tabCache[nextTabData.id] = res.data;
+        this.loadedTabs.add(nextIndex);
+      } catch (error) {
+        console.error("预加载tab数据失败:", error);
+      } finally {
+        // 移除加载标记
+        this.loadingTabs.delete(nextIndex);
+      }
     },
     save() {
       let data = this.setting[this.tabIndex];
@@ -1079,6 +1177,18 @@ export default {
           ...this.setting[this.tabIndex],
           id: e.data.id,
         });
+        
+        // 更新缓存中的数据
+        const currentTabId = this.setting[this.tabIndex].id;
+        if (currentTabId) {
+          // 重新获取最新数据并更新缓存
+          selectRulesInfo(currentTabId).then(res => {
+            this.tabCache[currentTabId] = res.data;
+            this.loadedTabs.add(parseInt(this.tabIndex));
+          }).catch(error => {
+            console.error("更新缓存失败:", error);
+          });
+        }
       }).catch(() => {
         this.loading = false;
       });

+ 17 - 1
src/views/qw/sopUserLogs/sopUserLogsSchedule.vue

@@ -84,6 +84,18 @@
         </el-tooltip>
       </el-col>
 
+      <el-col :span="1.5">
+        <el-tooltip class="item" effect="dark" content="修改选择的群聊营期时间" placement="top">
+          <el-button
+            type="danger"
+            icon="el-icon-edit"
+            size="medium"
+            :disabled="multiple"
+            @click="updateGroupTime"
+          >批量修改营期时间</el-button>
+        </el-tooltip>
+      </el-col>
+
       <el-col :span="1.5">
         <el-tooltip class="item" effect="dark" content="删除营期之后,将不会在给原营期的客户发送消息,ps:删除之后不可恢复" placement="top">
           <el-button
@@ -613,7 +625,11 @@ export default {
         date: this.updateTimeData.form.date,
         ids: this.ids,
       }
-      updateLogDate(form).then(e => {
+      updateLogDate(form).then(response => {
+        if (response.data!=null){
+          this.msgInfo("部分时间已有营期:"+response.data);
+        }
+
         this.updateTimeData.open = false;
         this.getList();
       });

+ 60 - 1
src/views/qw/user/cuDeptIdIndex.vue

@@ -12,6 +12,9 @@
           />
         </el-select>
       </el-form-item>
+      <el-form-item label="企微部门">
+        <treeselect :clearable="false"  v-model="queryParams.deptId"  :options="deptOptions" :show-count="true" placeholder="请选择归属部门"/>
+      </el-form-item>
       <el-form-item label="企微账号" prop="qwUserId">
         <el-input
           v-model="queryParams.qwUserId"
@@ -41,6 +44,16 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
+      <el-form-item label="ipad状态" prop="loginStatus">
+        <el-select v-model="queryParams.loginStatus" placeholder="请选择ipad状态" clearable>
+          <el-option
+            v-for="item in loginStatusOption"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value">
+          </el-option>
+        </el-select>
+      </el-form-item>
       <el-form-item label="员工状态" prop="isDel">
         <el-select v-model="queryParams.isDel" placeholder="请选择员工状态" clearable>
           <el-option
@@ -77,6 +90,7 @@
       <el-table-column label="企微账号" align="center" prop="qwUserId" />
       <el-table-column label="企微昵称" align="center" prop="qwUserName" />
       <el-table-column label="员工称呼" align="center" prop="welcomeText" />
+      <el-table-column label="部门" align="center" prop="departmentName" />
       <el-table-column label="员工状态" align="center" prop="isDel">
         <template slot-scope="scope">
           <el-tag v-if="scope.row.isDel == 0" type="success">正常</el-tag>
@@ -460,12 +474,16 @@ import {
   qwBindCloudHost, qwUnbindCloudHost, handleAuthAppKey, handleInputAuthAppKey, selectCloudAP, myDepartListUser
 } from '../../../api/qw/user'
 import fastGptRole from "@/views/fastGpt/fastGptRole/fastGptRole";
+import { treeselect } from "@/api/qw/qwDept";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
 
 export default {
   name: "cuDeptIdIndex",
-  components: { fastGptRole},
+  components: { Treeselect, fastGptRole},
   data() {
     return {
+      deptOptions:[], // 企微部门
       updateIp:{
         open:false,
         title: "修改云主机IP"
@@ -493,6 +511,13 @@ export default {
         value: 2,
         label: '离职'
       }],
+      loginStatusOption: [{
+        value: 0,
+        label: '离线'
+      }, {
+        value: 1,
+        label: '在线'
+      }],
       // 遮罩层
       loading: true,
       // 导出遮罩层
@@ -565,8 +590,10 @@ export default {
         pageNum: 1,
         pageSize: 10,
         qwUserId: null,
+        loginStatus: null,
         corpId: null,
         qwUserName: null,
+        deptId:null,
       },
 	  qwUserId:null,
       companyUserList:[],
@@ -595,6 +622,10 @@ export default {
       this.myQwCompanyList = response.data;
       if(this.myQwCompanyList!=null){
         this.queryParams.corpId=this.myQwCompanyList[0].dictValue;
+        // 查询部门下拉树结构
+        if(this.queryParams.corpId){
+          this.getTreeselect()
+        }
         this.getList();
       }
     });
@@ -612,6 +643,25 @@ export default {
     },
   },
   methods: {
+    /** 查询部门下拉树结构 */
+    getTreeselect() {
+      var that=this;
+      let query = {
+        corpId: this.queryParams.corpId
+      }
+      // 企微主体不能为空
+      if(!query.corpId){
+        this.$message.error("请选择企微主体");
+        return;
+      }
+      treeselect(query).then((response) => {
+        this.deptOptions = response.data;
+        console.log(this.deptOptions)
+        if(response.data!=null&&response.data.length>0){
+          this.queryParams.deptId=response.data[0].id;
+        }
+      });
+    },
     getList() {
       this.loading = true;
       myDepartListUser(this.queryParams).then(response => {
@@ -989,6 +1039,7 @@ export default {
         qwUserId: null,
         corpId: null,
         qwUserName: null,
+
       };
       this.resetForm("form");
 
@@ -1039,7 +1090,9 @@ export default {
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm("queryForm");
+      this.queryParams.loginStatus = null;
       this.queryParams.corpId=this.myQwCompanyList[0].dictValue;
+      this.queryParams.deptId = this.deptOptions[0] && this.deptOptions[0].id;
       this.handleQuery();
     },
     // 多选框选中数据
@@ -1120,4 +1173,10 @@ export default {
   width: 80%;
 }
 
+.vue-treeselect{
+  width: 217px;
+  height: 36px;
+}
+
+
 </style>

+ 24 - 1
src/views/qw/user/index.vue

@@ -44,6 +44,17 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
+      <el-form-item label="ipad状态" prop="loginStatus">
+        <el-select v-model="queryParams.loginStatus" placeholder="请选择ipad状态" clearable>
+          <el-option
+            v-for="item in loginStatusOption"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value">
+          </el-option>
+        </el-select>
+      </el-form-item>
+
       <el-form-item label="员工状态" prop="isDel">
         <el-select v-model="queryParams.isDel" placeholder="请选择员工状态" clearable>
           <el-option
@@ -598,6 +609,13 @@ export default {
         value: 2,
         label: '离职'
       }],
+      loginStatusOption: [{
+        value: 0,
+        label: '离线'
+      }, {
+        value: 1,
+        label: '在线'
+      }],
       // 遮罩层
       loading: true,
       names: [],
@@ -677,6 +695,7 @@ export default {
         pageNum: 1,
         pageSize: 10,
         qwUserId: null,
+        loginStatus: null,
         corpId: null,
         qwUserName: null,
         deptId:null,
@@ -711,7 +730,9 @@ export default {
       if(this.myQwCompanyList && this.myQwCompanyList.length>0){
         this.queryParams.corpId=this.myQwCompanyList[0].dictValue;
         // 查询部门下拉树结构
-        this.getTreeselect()
+        if(this.queryParams.corpId){
+          this.getTreeselect()
+        }
         this.getList();
       }
     });
@@ -1197,6 +1218,8 @@ export default {
     resetQuery() {
       this.resetForm("queryForm");
       this.queryParams.corpId = this.myQwCompanyList[0].dictValue;
+      this.queryParams.loginStatus = null;
+      this.queryParams.deptId = this.deptOptions[0] && this.deptOptions[0].id;
       this.handleQuery();
     },
     // 多选框选中数据

+ 21 - 0
src/views/qw/user/myIndex.vue

@@ -41,6 +41,18 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
+
+      <el-form-item label="ipad状态" prop="loginStatus">
+        <el-select v-model="queryParams.loginStatus" placeholder="请选择ipad状态" clearable>
+          <el-option
+            v-for="item in loginStatusOption"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value">
+          </el-option>
+        </el-select>
+      </el-form-item>
+
       <el-form-item label="员工状态" prop="isDel">
         <el-select v-model="queryParams.isDel" placeholder="请选择员工状态" clearable>
           <el-option
@@ -675,9 +687,17 @@ export default {
         pageNum: 1,
         pageSize: 10,
         qwUserId: null,
+        loginStatus: null,
         corpId: null,
         qwUserName: null,
       },
+      loginStatusOption: [{
+        value: 0,
+        label: '离线'
+      }, {
+        value: 1,
+        label: '在线'
+      }],
       qwUserId:null,
       companyUserList:[],
       // 表单参数
@@ -1172,6 +1192,7 @@ export default {
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm("queryForm");
+      this.queryParams.loginStatus = null;
       this.queryParams.corpId = this.myQwCompanyList[0].dictValue;
       this.handleQuery();
     },