Browse Source

同步scrm代码

Long 4 weeks ago
parent
commit
8683f21d45
100 changed files with 27496 additions and 1861 deletions
  1. 9 0
      src/api/company/company.js
  2. 37 1
      src/api/company/companyUser.js
  3. 53 0
      src/api/course/courseFinishTemp.js
  4. 18 1
      src/api/course/courseQuestionBank.js
  5. 46 0
      src/api/course/courseWatchLog.js
  6. 62 0
      src/api/course/qw/courseAnswerlogs.js
  7. 114 0
      src/api/course/qw/courseWatchLog.js
  8. 8 0
      src/api/course/userCourseCamp.js
  9. 89 0
      src/api/course/userCoursePeriod.js
  10. 34 0
      src/api/course/userCourseVideo.js
  11. 11 2
      src/api/course/videoResource.js
  12. 54 0
      src/api/qw/material.js
  13. 53 0
      src/api/qw/materialGroup.js
  14. 123 0
      src/api/qw/sop.js
  15. 150 0
      src/api/qw/sopTemp.js
  16. 61 0
      src/api/qw/tag.js
  17. 67 0
      src/api/qw/tagGroup.js
  18. 259 0
      src/api/qw/user.js
  19. 53 0
      src/api/qw/userVideo.js
  20. 53 0
      src/api/sop/companySopRole.js
  21. 12 0
      src/api/statistics/member.js
  22. 49 0
      src/api/statistics/statistics.js
  23. 8 0
      src/api/store/storeProduct.js
  24. 18 0
      src/api/store/user.js
  25. 82 0
      src/api/users/user.js
  26. 1 1
      src/assets/styles/element-variables.scss
  27. 3 3
      src/assets/styles/variables.scss
  28. 6 0
      src/components/ImageUpload/index.vue
  29. 1 1
      src/components/ThemePicker/index.vue
  30. 3 3
      src/components/TopNav/index.vue
  31. 11 0
      src/directive/select/elSelectLoadMore.js
  32. 7 0
      src/directive/select/index.js
  33. 2 0
      src/main.js
  34. 28 3
      src/router/index.js
  35. 1 1
      src/utils/cos.js
  36. 1 1
      src/views/company/CompanyRedPackageLogs/index.vue
  37. 3 1
      src/views/company/company/index.vue
  38. 21 9
      src/views/company/companyRecharge/index.vue
  39. 262 10
      src/views/components/course/userCourseCatalogDetails.vue
  40. 916 0
      src/views/course/courseFinishTemp/index.vue
  41. 148 22
      src/views/course/courseQuestionBank/index.vue
  42. 32 11
      src/views/course/courseTrafficLog/index.vue
  43. 43 0
      src/views/course/courseUserStatistics/courseUserStatisticsTabIndex.vue
  44. 432 0
      src/views/course/courseUserStatistics/index.vue
  45. 410 0
      src/views/course/courseUserStatistics/my.vue
  46. 711 0
      src/views/course/courseUserStatistics/myStatistics.vue
  47. 43 0
      src/views/course/courseUserStatistics/myStatisticsTabIndex.vue
  48. 43 0
      src/views/course/courseUserStatistics/myTabIndex.vue
  49. 384 0
      src/views/course/courseUserStatistics/qw/index.vue
  50. 381 0
      src/views/course/courseUserStatistics/qw/my.vue
  51. 680 0
      src/views/course/courseUserStatistics/qw/myStatistics.vue
  52. 697 0
      src/views/course/courseUserStatistics/qw/statistics.vue
  53. 752 0
      src/views/course/courseUserStatistics/statistics.vue
  54. 43 0
      src/views/course/courseUserStatistics/statisticsTabIndex.vue
  55. 178 34
      src/views/course/courseWatchLog/index.vue
  56. 388 0
      src/views/course/courseWatchLog/myCourseWatchLog.vue
  57. 43 0
      src/views/course/courseWatchLog/myWatchLogTabIndex.vue
  58. 489 0
      src/views/course/courseWatchLog/qw/index.vue
  59. 396 0
      src/views/course/courseWatchLog/qw/myCourseWatchLog.vue
  60. 318 0
      src/views/course/courseWatchLog/qw/statistics.vue
  61. 602 0
      src/views/course/courseWatchLog/qw/watchLog.vue
  62. 314 0
      src/views/course/courseWatchLog/qw/watchLogStatistics.vue
  63. 390 0
      src/views/course/courseWatchLog/statistics.vue
  64. 43 0
      src/views/course/courseWatchLog/statisticsTabIndex.vue
  65. 631 0
      src/views/course/courseWatchLog/watchLog.vue
  66. 321 0
      src/views/course/courseWatchLog/watchLogStatistics.vue
  67. 43 0
      src/views/course/courseWatchLog/watchLogStatisticsTabIndex.vue
  68. 43 0
      src/views/course/courseWatchLog/watchLogTabIndex.vue
  69. 226 379
      src/views/course/userCourse/index.vue
  70. 929 0
      src/views/course/userCourse/public.vue
  71. 126 0
      src/views/course/userCoursePeriod/batchRedPacket.vue
  72. 575 124
      src/views/course/userCoursePeriod/index.vue
  73. 173 0
      src/views/course/userCoursePeriod/redPacket.vue
  74. 338 0
      src/views/course/userCoursePeriod/statistics.vue
  75. 1261 77
      src/views/course/videoResource/index.vue
  76. 366 353
      src/views/crm/customer/index.vue
  77. 28 28
      src/views/crm/event/index.vue
  78. 156 70
      src/views/fs/user/index.vue
  79. 1648 106
      src/views/index.vue
  80. 392 392
      src/views/oms/orderInternal/add.vue
  81. 59 59
      src/views/qw/forbiddenMessage/index.vue
  82. 427 0
      src/views/qw/friendMaterial/index.vue
  83. 41 41
      src/views/qw/qwCompany/index.vue
  84. 213 0
      src/views/qw/sop/ImageUpload.vue
  85. 331 0
      src/views/qw/sopTemp/addAiChatTemp.vue
  86. 981 0
      src/views/qw/sopTemp/addSopTemp.vue
  87. 734 0
      src/views/qw/sopTemp/addSopTempOld.vue
  88. 194 0
      src/views/qw/sopTemp/addTemp.vue
  89. 680 0
      src/views/qw/sopTemp/index.vue
  90. 177 0
      src/views/qw/sopTemp/sopTemp.vue
  91. 327 0
      src/views/qw/sopTemp/updateAiChatTemp.vue
  92. 1741 0
      src/views/qw/sopTemp/updateSopTemp.vue
  93. 906 0
      src/views/qw/sopTemp/updateSopTemp2.vue
  94. 664 0
      src/views/qw/sopTemp/updateSopTempOld.vue
  95. 201 0
      src/views/qw/sopTemp/updateTemp.vue
  96. 274 0
      src/views/qw/userVideo/userVideo.vue
  97. 22 22
      src/views/qw/words/index.vue
  98. 310 0
      src/views/sop/companySopRole/index.vue
  99. 560 106
      src/views/statistics/index.vue
  100. 649 0
      src/views/statistics/member/index.vue

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

@@ -99,3 +99,12 @@ export function crmDayCountlist(query) {
     params: query
   })
 }
+
+// 获取公司选项列表
+export function getCompanyListLikeName(query) {
+  return request({
+    url: '/company/company/getCompanyListLikeName',
+    method: 'get',
+    params: query
+  })
+}

+ 37 - 1
src/api/company/companyUser.js

@@ -66,4 +66,40 @@ export function exportCompanyUser(query) {
     method: 'get',
     params: query
   })
-}
+}
+
+
+export function getUserList(companyId){
+  return request({
+    url: '/company/companyUser/getUserList?companyId='+companyId,
+    method: 'get'
+  })
+}
+
+// 根据登录的用户公司获取所有的销售
+export function getCompanyUserList(query) {
+  return request({
+    url: '/company/companyUser/getCompanyUserList',
+    method: 'get',
+    query: query
+  })
+}
+
+//更换会员归属销售
+export function changeCompanyUser(data, params) {
+  return request({
+    url: '/company/companyUser/changeCompanyUser',
+    method: 'post',
+    data: data,
+    params: params
+  })
+}
+
+// 获取销售选项列表
+export function getCompanyUserListLikeName(query){
+  return request({
+    url: '/company/companyUser/getCompanyUserListLikeName',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/course/courseFinishTemp.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询完课模板列表
+export function listCourseFinishTemp(query) {
+  return request({
+    url: '/course/courseFinishTemp/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询完课模板详细
+export function getCourseFinishTemp(id) {
+  return request({
+    url: '/course/courseFinishTemp/' + id,
+    method: 'get'
+  })
+}
+
+// 新增完课模板
+export function addCourseFinishTemp(data) {
+  return request({
+    url: '/course/courseFinishTemp',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改完课模板
+export function updateCourseFinishTemp(data) {
+  return request({
+    url: '/course/courseFinishTemp',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除完课模板
+export function delCourseFinishTemp(id) {
+  return request({
+    url: '/course/courseFinishTemp/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出完课模板
+export function exportCourseFinishTemp(query) {
+  return request({
+    url: '/course/courseFinishTemp/export',
+    method: 'get',
+    params: query
+  })
+}

+ 18 - 1
src/api/course/courseQuestionBank.js

@@ -50,4 +50,21 @@ export function exportCourseQuestionBank(query) {
     method: 'get',
     params: query
   })
-}
+}
+
+// 下载模板
+export function importTemplate() {
+  return request({
+    url: '/course/courseQuestionBank/importTemplate',
+    method: 'get'
+  })
+}
+
+// 根据ids查询
+export function getByIds(query) {
+  return request({
+    url: '/course/courseQuestionBank/getByIds',
+    method: 'get',
+    params: query
+  })
+}

+ 46 - 0
src/api/course/courseWatchLog.js

@@ -51,3 +51,49 @@ export function exportCourseWatchLog(query) {
     params: query
   })
 }
+
+
+export function statisticsList(query) {
+  return request({
+    url: '/course/courseWatchLog/statisticsList',
+    method: 'get',
+    params: query
+  })
+}
+
+export function qwWatchLogStatisticsList(query) {
+  return request({
+    url: '/course/courseWatchLog/qwWatchLogStatisticsList',
+    method: 'get',
+    params: query
+  })
+}
+
+export function qwWatchLogAllStatisticsList(query) {
+  return request({
+    url: '/course/courseWatchLog/qwWatchLogAllStatisticsList',
+    method: 'get',
+    params: query
+  })
+}
+export function myQwWatchLogStatisticsList(query) {
+  return request({
+    url: '/course/courseWatchLog/myQwWatchLogStatisticsList',
+    method: 'get',
+    params: query
+  })
+}
+export function myQwWatchLogAllStatisticsList(query) {
+  return request({
+    url: '/course/courseWatchLog/myQwWatchLogAllStatisticsList',
+    method: 'get',
+    params: query
+  })
+}
+export function watchLogStatistics(query) {
+  return request({
+    url: '/course/courseWatchLog/watchLogStatistics',
+    method: 'get',
+    params: query
+  })
+}

+ 62 - 0
src/api/course/qw/courseAnswerlogs.js

@@ -0,0 +1,62 @@
+import request from '@/utils/request'
+
+// 查询答题日志列表
+export function listLogs(query) {
+  return request({
+    url: '/qw/course/courseAnswerLog/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function myListLogs(query) {
+  return request({
+    url: '/qw/course/courseAnswerLog/myList',
+    method: 'get',
+    params: query
+  })
+}
+
+
+// 查询答题日志详细
+export function getLogs(logId) {
+  return request({
+    url: '/qw/course/courseAnswerLog/' + logId,
+    method: 'get'
+  })
+}
+
+// 新增答题日志
+export function addLogs(data) {
+  return request({
+    url: '/qw/course/courseAnswerLog',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改答题日志
+export function updateLogs(data) {
+  return request({
+    url: '/qw/course/courseAnswerLog',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除答题日志
+export function delLogs(logId) {
+  return request({
+    url: '/qw/course/courseAnswerLog/' + logId,
+    method: 'delete'
+  })
+}
+
+// 导出答题日志
+export function exportLogs(query) {
+  return request({
+    url: '/qw/course/courseAnswerLog/export',
+    method: 'get',
+    params: query
+  })
+}

+ 114 - 0
src/api/course/qw/courseWatchLog.js

@@ -0,0 +1,114 @@
+import request from '@/utils/request'
+
+// 查询短链课程看课记录列表
+export function listCourseWatchLog(query) {
+  return request({
+    url: '/qw/course/courseWatchLog/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function myListCourseWatchLog(query) {
+  return request({
+    url: '/qw/course/courseWatchLog/myList',
+    method: 'get',
+    params: query
+  })
+}
+export function statisticsList(query) {
+  return request({
+    url: '/qw/course/courseWatchLog/statisticsList',
+    method: 'get',
+    params: query
+  })
+}
+export function qwWatchLogStatisticsList(query) {
+  return request({
+    url: '/qw/course/courseWatchLog/qwWatchLogStatisticsList',
+    method: 'get',
+    params: query
+  })
+}
+export function myQwWatchLogStatisticsList(query) {
+  return request({
+    url: '/qw/course/courseWatchLog/myQwWatchLogStatisticsList',
+    method: 'get',
+    params: query
+  })
+}
+
+
+export function qwWatchLogAllStatisticsList(query) {
+  return request({
+    url: '/qw/course/courseWatchLog/qwWatchLogAllStatisticsList',
+    method: 'get',
+    params: query
+  })
+}
+export function myQwWatchLogAllStatisticsList(query) {
+  return request({
+    url: '/qw/course/courseWatchLog/myQwWatchLogAllStatisticsList',
+    method: 'get',
+    params: query
+  })
+}
+
+export function watchLogStatistics(query) {
+  return request({
+    url: '/qw/course/courseWatchLog/watchLogStatistics',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询短链课程看课记录详细
+export function getCourseWatchLog(logId) {
+  return request({
+    url: '/qw/course/courseWatchLog/' + logId,
+    method: 'get'
+  })
+}
+
+// 新增短链课程看课记录
+export function addCourseWatchLog(data) {
+  return request({
+    url: '/qw/course/courseWatchLog',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改短链课程看课记录
+export function updateCourseWatchLog(data) {
+  return request({
+    url: '/qw/course/courseWatchLog',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除短链课程看课记录
+export function delCourseWatchLog(logId) {
+  return request({
+    url: '/qw/course/courseWatchLog/' + logId,
+    method: 'delete'
+  })
+}
+
+// 导出短链课程看课记录
+export function exportCourseWatchLog(query) {
+  return request({
+    url: '/qw/course/courseWatchLog/export',
+    method: 'get',
+    params: query
+  })
+}
+
+export function watchLogStatisticsExport(query) {
+  return request({
+    url: '/qw/course/courseWatchLog/watchLogStatisticsExport',
+    method: 'get',
+    params: query
+  })
+}

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

@@ -43,4 +43,12 @@ export function copyCamp(trainingCampId) {
   })
 }
 
+// 获取训练营选项列表
+export function getCampListLikeName(query) {
+  return request({
+    url: '/course/trainingCamp/getCampListLikeName',
+    method: 'get',
+    params: query
+  })
+}
 

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

@@ -51,6 +51,23 @@ export function addCourse(data) {
     data: data
   })
 }
+
+// 新增会员营期
+export function updateCourseTime(data) {
+  return request({
+    url: '/course/period/updateCourseTime',
+    method: 'post',
+    data: data
+  })
+}
+// 新增会员营期
+export function updateCourseDate(data) {
+  return request({
+    url: '/course/period/updateCourseDate',
+    method: 'post',
+    data: data
+  })
+}
 // 新增会员营期
 export function updateListCourseData(data) {
   return request({
@@ -85,3 +102,75 @@ export function exportPeriod(query) {
     params: query
   })
 }
+
+// 根据营期id获取公司红包金额列表
+export function getPeriodCompanyList(query) {
+  return request({
+    url: '/course/period/companyList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 按照课程批量设置红包金额
+export function batchSaveRedPacket(data) {
+  return request({
+    url: '/course/period/batchRedPacket',
+    method: 'post',
+    data: data
+  })
+}
+
+// 获取设置红包金额列表展示
+export function getPeriodRedPacketList(query) {
+  return request({
+    url: '/course/period/redPacketList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 按照营期批量设置红包金额
+export function batchSaveRedPacketByPeriod(data) {
+  return request({
+    url: '/course/period/batchRedPacket/byPeriod',
+    method: 'post',
+    data: data
+  })
+}
+
+// 按照营期、按课程统计
+export function periodCountSelect(data) {
+  return request({
+    url: '/course/period/periodCount',
+    method: 'post',
+    data: data
+  })
+}
+
+// 获取营期选项列表
+export function getPeriodListLikeName(query) {
+  return request({
+    url: '/course/period/getPeriodListLikeName',
+    method: 'get',
+    params: query
+  })
+}
+
+// 营期课程上移下移
+export function periodCourseMove(data) {
+  return request({
+    url: '/course/period/courseMove',
+    method: 'put',
+    params: data
+  })
+}
+
+// 结束营期
+export function closePeriod(query) {
+  return request({
+    url: '/course/period/closePeriod',
+    method: 'post',
+    params: query
+  })
+}

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

@@ -32,6 +32,31 @@ export function addUserCourseVideo(data) {
     data: data
   })
 }
+// 新增课堂视频
+export function updates(data) {
+  return request({
+    url: '/course/userCourseVideo/updates',
+    method: 'post',
+    data: data
+  })
+}
+
+// 新增课堂视频
+export function batchSaveVideo(data) {
+  return request({
+    url: '/course/userCourseVideo/batchSaveVideo',
+    method: 'post',
+    data: data
+  })
+}
+// 新增课堂视频
+export function batchUpdateRed(data) {
+  return request({
+    url: '/course/userCourseVideo/batchUpdateRed',
+    method: 'post',
+    data: data
+  })
+}
 
 // 修改课堂视频
 export function updateUserCourseVideo(data) {
@@ -68,4 +93,13 @@ export function getVideoListByCourseId(query) {
   })
 }
 
+// 获取选项列表
+export function getVideoListLikeName(query) {
+  return request({
+    url: '/course/userCourseVideo/getVideoListLikeName',
+    method: 'get',
+    params: query
+  })
+}
+
 

+ 11 - 2
src/api/course/videoResource.js

@@ -21,13 +21,13 @@ export function getVideoResource(resourceId) {
 export function addVideoResource(data) {
   return request({
     url: '/course/videoResource',
-    method: 'post', 
+    method: 'post',
     data: data
   })
 }
 
 // 修改视频资源
-export function updateVideoResource(data) { 
+export function updateVideoResource(data) {
   return request({
     url: '/course/videoResource',
     method: 'put',
@@ -43,3 +43,12 @@ export function deleteVideoResource(resourceId) {
   })
 }
 
+// 批量新增视频资源
+export function batchAddVideoResource(data) {
+  return request({
+    url: '/course/videoResource/batchAddVideoResource',
+    method: 'post',
+    data: data
+  })
+}
+

+ 54 - 0
src/api/qw/material.js

@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+
+// 查询素材库列表
+export function listMaterial(query) {
+  return request({
+    url: '/qw/material/list',
+    method: 'get',
+    params: query
+  })
+}
+
+
+// 查询素材库详细
+export function getMaterial(materialId) {
+  return request({
+    url: '/qw/material/' + materialId,
+    method: 'get'
+  })
+}
+
+// 新增素材库
+export function addMaterial(data) {
+  return request({
+    url: '/qw/material',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改素材库
+export function updateMaterial(data) {
+  return request({
+    url: '/qw/material',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除素材库
+export function delMaterial(materialId) {
+  return request({
+    url: '/qw/material/' + materialId,
+    method: 'delete'
+  })
+}
+
+// 导出素材库
+export function exportMaterial(query) {
+  return request({
+    url: '/qw/material/export',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/qw/materialGroup.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询企业微信素材分组列表
+export function listMaterialGroup(query) {
+  return request({
+    url: '/qw/materialGroup/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询企业微信素材分组详细
+export function getMaterialGroup(materialGroupId) {
+  return request({
+    url: '/qw/materialGroup/' + materialGroupId,
+    method: 'get'
+  })
+}
+
+// 新增企业微信素材分组
+export function addMaterialGroup(data) {
+  return request({
+    url: '/qw/materialGroup',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改企业微信素材分组
+export function updateMaterialGroup(data) {
+  return request({
+    url: '/qw/materialGroup',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除企业微信素材分组
+export function delMaterialGroup(materialGroupId) {
+  return request({
+    url: '/qw/materialGroup/' + materialGroupId,
+    method: 'delete'
+  })
+}
+
+// 导出企业微信素材分组
+export function exportMaterialGroup(query) {
+  return request({
+    url: '/qw/materialGroup/export',
+    method: 'get',
+    params: query
+  })
+}

+ 123 - 0
src/api/qw/sop.js

@@ -0,0 +1,123 @@
+import request from '@/utils/request'
+
+// 查询企微sop列表
+export function listSop(query) {
+  return request({
+    url: '/qw/sop/list',
+    method: 'get',
+    params: query
+  })
+}
+// 查询企微sop模板列表
+export function listAiChatSop(query) {
+  return request({
+    url: '/qw/sop/listAiChatSop',
+    method: 'get',
+    params: query
+  })
+}
+
+
+export function courseList() {
+  return request({
+    url: '/qw/sop/courseList',
+    method: 'get',
+  })
+}
+export function videoList(id) {
+  return request({
+    url: '/qw/sop/videoList/' + id,
+    method: 'get'
+  })
+}
+// 查询企微sop详细
+export function getSop(id) {
+  return request({
+    url: '/qw/sop/' + id,
+    method: 'get'
+  })
+}
+
+// 新增企微sop
+export function addSop(data) {
+  return request({
+    url: '/qw/sop',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改企微sop
+export function updateSop(data) {
+  return request({
+    url: '/qw/sop',
+    method: 'put',
+    data: data
+  })
+}
+//修改企微sop自动创建时间
+export function updateAutoSopTime(data) {
+  return request({
+    url: '/qw/sop/updateAutoSopTime',
+    method: 'post',
+    data: data
+  })
+}
+
+//修改状态
+export function updateSopStatus(data) {
+  return request({
+    url: '/qw/sop/updateSopStatus',
+    method: 'post',
+    data: data
+  })
+}
+// 删除企微sop
+export function delSop(id) {
+  return request({
+    url: '/qw/sop/' + id,
+    method: 'delete'
+  })
+}
+
+//批量执行企微sop
+export function executeSop(ids) {
+  return request({
+    url: '/qw/sop/executeSop/' + ids,
+    method: 'put'
+  })
+}
+//批量执行企微sop先改状态
+export function updateStatus(ids) {
+  return request({
+    url: '/qw/sop/updateStatus/' + ids,
+    method: 'get'
+  })
+}
+// 导出企微sop
+export function exportSop(query) {
+  return request({
+    url: '/qw/sop/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出企微sop
+export function getSopVoiceList(params) {
+  return request({
+    url: '/qw/sop/getSopVoiceList',
+    method: 'get',
+    params
+  })
+}
+
+
+// 修改企微sop
+export function updateSopQwUser(data) {
+  return request({
+    url: '/qw/sop/updateSopQwUser',
+    method: 'post',
+    data: data
+  })
+}

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

@@ -0,0 +1,150 @@
+import request from '@/utils/request'
+
+// 查询sop模板列表
+export function listSopTemp(query) {
+  return request({
+    url: '/qw/sopTemp/list',
+    method: 'get',
+    params: query
+  })
+}
+// 查询sop模板列表
+export function redList(id) {
+  return request({
+    url: '/qw/sopTemp/redList',
+    method: 'get',
+    params: {id}
+  })
+}
+
+// 查询sop模板详细
+export function getSopTemp(id) {
+  return request({
+    url: '/qw/sopTemp/' + id,
+    method: 'get'
+  })
+}
+export function dayListFun(id) {
+  return request({
+    url: '/qw/sopTemp/dayList',
+    method: 'get',
+    params: {id}
+  })
+}
+// 查询sop模板详细
+export function sortDay(list) {
+  return request({
+    url: '/qw/sopTemp/sortDay',
+    method: 'post',
+    data: list
+  })
+}
+// 查询sop模板详细
+export function selectRulesInfo(id, name, dayNum) {
+  return request({
+    url: '/qw/sopTemp/selectRulesInfo',
+    method: 'get',
+    params: {id, name, dayNum}
+  })
+}
+// 查询sop模板详细
+export function delRules(id, name, dayNum) {
+  return request({
+    url: '/qw/sopTemp/delRules',
+    method: 'get',
+    params: {id, name, dayNum}
+  })
+}
+
+// 新增sop模板
+export function addSopTemp(data) {
+  return request({
+    url: '/qw/sopTemp',
+    method: 'post',
+    data: data
+  })
+}
+// 新增sop模板
+export function updateRedPackage(data) {
+  return request({
+    url: '/qw/sopTemp/updateRedPackage',
+    method: 'post',
+    data: data
+  })
+}
+// 新增sop模板
+export function addTemp(data) {
+  return request({
+    url: '/qw/sopTemp/add',
+    method: 'post',
+    data: data
+  })
+}
+
+// 新增sop模板
+export function copyTemplate(data) {
+  return request({
+    url: '/qw/sopTemp/copyTemplate',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改sop模板
+export function updateTemp(data) {
+  return request({
+    url: '/qw/sopTemp/update',
+    method: 'post',
+    data: data
+  })
+}
+// 修改sop模板
+export function addOrUpdateSetting(data) {
+  return request({
+    url: '/qw/sopTemp/addOrUpdateSetting',
+    method: 'post',
+    data: data
+  })
+}
+// 修改sop模板
+export function updateSopTemp(data) {
+  return request({
+    url: '/qw/sopTemp',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除sop模板
+export function delSopTemp(id) {
+  return request({
+    url: '/qw/sopTemp/' + id,
+    method: 'delete'
+  })
+}
+
+
+export function shareSopTemp(data) {
+  return request({
+    url: '/qw/sopTemp/shareTemp',
+    method: 'post',
+    data: data
+  })
+}
+
+// 导出sop模板
+export function exportSopTemp(query) {
+  return request({
+    url: '/qw/sopTemp/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出sop模板
+export function getSelectableRange() {
+  return request({
+    url: '/qw/sopTemp/getSelectableRange',
+    method: 'get'
+  })
+}

+ 61 - 0
src/api/qw/tag.js

@@ -0,0 +1,61 @@
+import request from '@/utils/request'
+
+// 查询企微客户标签列表
+export function listTag(query) {
+  return request({
+    url: '/qw/tag/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询企微客户标签详细
+export function getTag(id) {
+  return request({
+    url: '/qw/tag/' + id,
+    method: 'get'
+  })
+}
+
+// 新增企微客户标签
+export function addTag(data) {
+  return request({
+    url: '/qw/tag',
+    method: 'post',
+    data: data
+  })
+}
+
+export function searchTags(data) {
+  return request({
+    url: '/qw/tag/searchTags',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改企微客户标签
+export function updateTag(data) {
+  return request({
+    url: '/qw/tag',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除企微客户标签
+export function delTag(id) {
+  return request({
+    url: '/qw/tag/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出企微客户标签
+export function exportTag(query) {
+  return request({
+    url: '/qw/tag/export',
+    method: 'get',
+    params: query
+  })
+}

+ 67 - 0
src/api/qw/tagGroup.js

@@ -0,0 +1,67 @@
+import request from '@/utils/request'
+
+// 查询企微客户标签组列表
+export function listTagGroup(query) {
+  return request({
+    url: '/qw/tagGroup/list',
+    method: 'get',
+    params: query
+  })
+}
+export function allListTagGroup(query) {
+  return request({
+    url: '/qw/tagGroup/allList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询企微客户标签组详细
+export function getTagGroup(id) {
+  return request({
+    url: '/qw/tagGroup/' + id,
+    method: 'get'
+  })
+}
+
+// 新增企微客户标签组
+export function addTagGroup(data) {
+  return request({
+    url: '/qw/tagGroup',
+    method: 'post',
+    data: data
+  })
+}
+export function syncTag(id) {
+  return request({
+    url: '/qw/tagGroup/syncTag/' + id,
+    method: 'post',
+
+  })
+}
+
+// 修改企微客户标签组
+export function updateTagGroup(data) {
+  return request({
+    url: '/qw/tagGroup',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除企微客户标签组
+export function delTagGroup(id) {
+  return request({
+    url: '/qw/tagGroup/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出企微客户标签组
+export function exportTagGroup(query) {
+  return request({
+    url: '/qw/tagGroup/export',
+    method: 'get',
+    params: query
+  })
+}

+ 259 - 0
src/api/qw/user.js

@@ -0,0 +1,259 @@
+import request from '@/utils/request'
+
+// 查询企微员工列表
+export function staffListUser(query) {
+  return request({
+    url: '/qw/user/staffList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询企微用户列表
+export function listUser(query) {
+  return request({
+    url: '/qw/user/list',
+    method: 'get',
+    params: query
+  })
+}
+// 查询企微用户列表
+export function userList(query) {
+  return request({
+    url: '/qw/user/userList',
+    method: 'get',
+    params: query
+  })
+}
+export function qwUserList(id) {
+  return request({
+    url: '/qw/user/qwUserList/' + id,
+    method: 'get',
+  })
+}
+export function getQwUserList(query) {
+  return request({
+    url: '/qw/user/getQwUserList',
+    method: 'get',
+    params: query
+  })
+}
+export function getMyQwUserList(query) {
+  return request({
+    url: '/qw/user/getMyQwUserList',
+    method: 'get',
+    params: query
+  })
+}
+export function getMyQwCompanyList(query) {
+  return request({
+    url: '/qw/user/getMyQwCompanyList',
+    method: 'get',
+    params: query
+  })
+}
+// 查询企微用户详细
+export function getQwUser(id) {
+  return request({
+    url: '/qw/user/' + id,
+    method: 'get'
+  })
+}
+//批量查询企微用户详细
+export function getQwUserByIds(ids) {
+  return request({
+    url: '/qw/user/getInfo/' + ids,
+    method: 'get'
+  })
+}
+
+// 新增企微用户
+export function addUser(data) {
+  return request({
+    url: '/qw/user',
+    method: 'post',
+    data: data
+  })
+}
+export function addQwUser(id) {
+  return request({
+    url: '/qw/user/sync/' + id,
+    method: 'post',
+  })
+}
+// 修改企微用户
+export function updateUser(data) {
+  return request({
+    url: '/qw/user',
+    method: 'put',
+    data: data
+  })
+}
+
+// 绑定Ai客服
+export function qwUserBindAi(data) {
+  return request({
+    url: '/qw/user/bindAi',
+    method: 'put',
+    data: data
+  })
+}
+
+//解绑AI客服
+export function relieveFastGptRoleById(id) {
+  return request({
+    url: '/qw/user/relieveFastGptRoleById/' + id,
+    method: 'get'
+  })
+}
+
+//绑定企微用户
+export function bindQwUser(data) {
+  return request({
+    url: '/qw/user/bindQwUser',
+    method: 'put',
+    data: data
+  })
+}
+
+//修改企微用户的欢迎语管理
+export function updateUserWeclome(data) {
+  return request({
+    url: '/qw/user/weclomeQwUser',
+    method: 'post',
+    data: data
+  })
+}
+
+
+
+// 删除企微用户
+export function delUser(id) {
+  return request({
+    url: '/qw/user/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出企微用户
+export function exportUser(query) {
+  return request({
+    url: '/qw/user/export',
+    method: 'get',
+    params: query
+  })
+}
+
+
+/**
+ * 登录企业微信(发起登录)
+ */
+export function loginQwCode(data) {
+  return request({
+    url: '/qw/user/loginQwCode',
+    method: 'post',
+    data: data
+  })
+}
+/**
+ * 取redis里的登录二维码
+ */
+export function getQwCodeUrl(data) {
+  return request({
+    url: '/qw/user/getQwCodeUrl',
+    method: 'post',
+    data: data
+  })
+}
+/**
+ * 登录请求-刷新获取二维码
+ */
+export function loginQwCodeUrl(data) {
+  return request({
+    url: '/qw/user/loginQwCodeUrl',
+    method: 'post',
+    data: data
+  })
+}
+/**
+ * 登录企业微信(传输验证信息)
+ */
+export function loginQwCodeMsg(data) {
+  return request({
+    url: '/qw/user/loginQwCodeMsg',
+    method: 'post',
+    data: data
+  })
+}
+/**
+ * 退出企业微信
+ */
+export function logoutQwLogout(data) {
+  return request({
+    url: '/qw/user/logoutQwLogout',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 查询登录状态
+ */
+export function getLoginQwStatus(data) {
+  return request({
+    url: '/qw/user/getLoginQwStatus',
+    method: 'post',
+    data: data
+  })
+}
+/**
+ * 企业微信员工账号 直接授权key
+ */
+export function handleAuthAppKey(data) {
+  return request({
+    url: '/qw/user/authAppKey',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 企业微信员工账号 输入的授权key
+ */
+export function handleInputAuthAppKey(data) {
+  return request({
+    url: '/qw/user/handleInputAuthAppKey',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+* 企业微信员工账号 绑定 云主机
+*/
+export function qwBindCloudHost(appkey) {
+  return request({
+    url: '/qw/user/qwBindCloudHost/'+appkey,
+    method: 'get',
+  })
+}
+/**
+ * 企业微信员工账号 解除绑定 云主机
+ */
+export function qwUnbindCloudHost(appkey) {
+  return request({
+    url: '/qw/user/qwUnbindCloudHost/'+appkey,
+    method: 'get',
+  })
+}
+
+/**
+ * 获取云主机的账密
+ */
+export function selectCloudAP(data) {
+  return request({
+    url: '/qw/user/selectCloudAP',
+    method: 'post',
+    data: data
+  })
+}

+ 53 - 0
src/api/qw/userVideo.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询企业微信的视频号列表
+export function listQwUserVideo(query) {
+  return request({
+    url: '/qw/userVideo/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询企业微信的视频号详细
+export function getQwUserVideo(id) {
+  return request({
+    url: '/qw/userVideo/' + id,
+    method: 'get'
+  })
+}
+
+// 新增企业微信的视频号
+export function addQwUserVideo(data) {
+  return request({
+    url: '/qw/userVideo',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改企业微信的视频号
+export function updateQwUserVideo(data) {
+  return request({
+    url: '/qw/userVideo',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除企业微信的视频号
+export function delQwUserVideo(id) {
+  return request({
+    url: '/qw/userVideo/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出企业微信的视频号
+export function exportQwUserVideo(query) {
+  return request({
+    url: '/qw/userVideo/export',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/sop/companySopRole.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询公司SOP权限列表
+export function listCompanySopRole(query) {
+  return request({
+    url: '/sop/companySopRole/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询公司SOP权限详细
+export function getCompanySopRole(id) {
+  return request({
+    url: '/sop/companySopRole/' + id,
+    method: 'get'
+  })
+}
+
+// 新增公司SOP权限
+export function addCompanySopRole(data) {
+  return request({
+    url: '/sop/companySopRole',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改公司SOP权限
+export function updateCompanySopRole(data) {
+  return request({
+    url: '/sop/companySopRole',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除公司SOP权限
+export function delCompanySopRole(id) {
+  return request({
+    url: '/sop/companySopRole/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出公司SOP权限
+export function exportCompanySopRole(query) {
+  return request({
+    url: '/sop/companySopRole/export',
+    method: 'get',
+    params: query
+  })
+}

+ 12 - 0
src/api/statistics/member.js

@@ -0,0 +1,12 @@
+import request from "@/utils/request"
+
+/**
+ * 获取会员统计数据
+ */
+export function dailyData(query) {
+  return request({
+    url: '/stats/member/dailyData',
+    method: 'get',
+    params: query
+  })
+}

+ 49 - 0
src/api/statistics/statistics.js

@@ -41,6 +41,30 @@ export function dealerAggregated() {
   })
 }
 
+/**
+ * 消费余额
+ * @returns {AxiosPromise}
+ */
+export function rechargeComsumption(){
+  return request({
+    url: '/index/statistics/rechargeComsumption',
+    method: 'get',
+    params: {}
+  })
+}
+/**
+ * 获取统计流量
+ * @returns {AxiosPromise}
+ */
+export function trafficLog(){
+  return request({
+    url: '/index/statistics/trafficLog',
+    method: 'get',
+    params: {}
+  })
+}
+
+
 
 /**
  * 数据概览
@@ -260,3 +284,28 @@ export function rewardMoneyTrend(param){
     data: safeParam
   })
 }
+
+
+/**
+ * 获取当月订单数
+ * @returns {*}
+ */
+export function thisMonthOrderCount(){
+  return request({
+    url: '/index/statistics/thisMonthOrderCount',
+    method: 'get',
+    params: {}
+  })
+}
+
+/**
+ * 获取当月收款数
+ * @returns {*}
+ */
+export function thisMonthRecvCount(){
+  return request({
+    url: '/index/statistics/thisMonthRecvCount',
+    method: 'get',
+    params: {}
+  })
+}

+ 8 - 0
src/api/store/storeProduct.js

@@ -9,6 +9,14 @@ export function listStoreProduct(query) {
   })
 }
 
+export function batchModify(param) {
+  return request({
+    url: '/store/storeProduct/batchModify',
+    method: 'post',
+    data: param
+  })
+}
+
 // 查询商品详细
 export function getStoreProduct(productId) {
   return request({

+ 18 - 0
src/api/store/user.js

@@ -83,3 +83,21 @@ export function getAllUserListLimit(query) {
     params: query
   })
 }
+
+// 获取小黑屋用户列表
+export function darkRoomList(query) {
+  return request({
+    url: '/store/user/darkRoomList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 批量解禁
+export function enabledUsers(data) {
+  return request({
+    url: '/store/user/enabledUsers',
+    method: 'post',
+    data: data
+  })
+}

+ 82 - 0
src/api/users/user.js

@@ -0,0 +1,82 @@
+import request from '@/utils/request'
+
+export function list(query) {
+  return request({
+    url: '/users/user/list',
+    method: 'get',
+    params: query
+  })
+}
+export function myList(query) {
+  return request({
+    url: '/users/user/myList',
+    method: 'get',
+    params: query
+  })
+}
+export function getUser(userId) {
+  return request({
+    url: '/users/user/' + userId,
+    method: 'get'
+  })
+}
+
+export function getUserList(query) {
+  return request({
+    url: '/users/user/getUserList',
+    method: 'get',
+    params: query
+  })
+}
+export function getFsUserList(query) {
+  return request({
+    url: '/store/user/getUserListLimit',
+    method: 'get',
+    params: query
+  })
+}
+// 新增用户
+export function addUser(data) {
+  return request({
+    url: '/users/user',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改用户
+export function updateUser(data) {
+  return request({
+    url: '/users/user',
+    method: 'put',
+    data: data
+  })
+}
+
+// 列出当前公司的客户
+export function listUser(data) {
+  return request({
+    url: '/fsuser/user/list',
+    method: 'get',
+    params: data
+  })
+}
+
+// 转移客户
+export function transferUser(data) {
+  return request({
+    url: '/fsuser/user/transfer',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询会员选项列表
+export function getUserListLikeName(query) {
+  return request({
+    url: '/user/fsUser/getUserListLikeName',
+    method: 'get',
+    params: query
+  })
+}
+

+ 1 - 1
src/assets/styles/element-variables.scss

@@ -4,7 +4,7 @@
 **/
 
 /* theme color */
-$--color-primary: #13c2c2;
+$--color-primary: #409eff;
 $--color-success: #13ce66;
 $--color-warning: #ffba00;
 $--color-danger: #ff4949;

+ 3 - 3
src/assets/styles/variables.scss

@@ -3,10 +3,10 @@ $blue:#324157;
 $light-blue:#3A71A8;
 $red:#C03639;
 $pink: #E65D6E;
-$green: #30B08F;
-$tiffany: #4AB7BD;
+$green: #409eff;
+$tiffany: #409eff;
 $yellow:#FEC171;
-$panGreen: #30B08F;
+$panGreen: #409eff;
 
 // sidebar
 $menuText:#bfcbd9;

+ 6 - 0
src/components/ImageUpload/index.vue

@@ -9,6 +9,7 @@
       :on-error="handleUploadError"
       :on-exceed="handleExceed"
       name="file"
+      :disabled="disabled"
       :on-remove="handleRemove"
       :show-file-list="true"
       :file-list="fileList"
@@ -56,6 +57,11 @@ export default {
        type: Number,
       default: 5,
     },
+    // 大小限制(MB)
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
     // 文件类型, 例如['png', 'jpg', 'jpeg']
     fileType: {
       type: Array,

+ 1 - 1
src/components/ThemePicker/index.vue

@@ -9,7 +9,7 @@
 
 <script>
 const version = require('element-ui/package.json').version // element-ui version from node_modules
-const ORIGINAL_THEME = '#13c2c2' // default color
+const ORIGINAL_THEME = '#409eff' // default color
 
 export default {
   data() {

+ 3 - 3
src/components/TopNav/index.vue

@@ -6,7 +6,7 @@
   >
     <template v-for="(item, index) in topMenus">
       <el-menu-item :index="item.path" :key="index" v-if="index < visibleNumber"
-        ><svg-icon :icon-class="item.meta.icon" />
+      ><svg-icon :icon-class="item.meta.icon" />
         {{ item.meta.title }}</el-menu-item
       >
     </template>
@@ -19,7 +19,7 @@
           :index="item.path"
           :key="index"
           v-if="index >= visibleNumber"
-          ><svg-icon :icon-class="item.meta.icon" />
+        ><svg-icon :icon-class="item.meta.icon" />
           {{ item.meta.title }}</el-menu-item
         >
       </template>
@@ -138,7 +138,7 @@ export default {
 }
 
 .el-menu--horizontal > .el-menu-item.is-active {
-  border-bottom: 3px solid #13c2c2;
+  border-bottom: 3px solid #409eff;
   color: #303133;
 }
 </style>

+ 11 - 0
src/directive/select/elSelectLoadMore.js

@@ -0,0 +1,11 @@
+export default {
+  inserted(el, binding, vnode) {
+    const SELECT_WRAP_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
+    SELECT_WRAP_DOM.addEventListener('scroll', function() {
+      const condition = this.scrollHeight - this.scrollTop <= this.clientHeight
+      if (condition) {
+        binding.value()
+      }
+    })
+  }
+}

+ 7 - 0
src/directive/select/index.js

@@ -0,0 +1,7 @@
+import elSelectLoadMore from './elSelectLoadMore'
+
+const install = function(Vue) {
+  Vue.directive('select-load-more', elSelectLoadMore)
+}
+
+export default install

+ 2 - 0
src/main.js

@@ -14,6 +14,7 @@ import App from './App'
 import store from './store'
 import router from './router'
 import permission from './directive/permission'
+import select from './directive/select'
 
 import VueClipboard from 'vue-clipboard2'
 
@@ -92,6 +93,7 @@ Vue.component('FileUpload', FileUpload)
 Vue.component('ImageUpload', ImageUpload)
 
 Vue.use(permission)
+Vue.use(select)
 
 /**
  * If you don't want to use mock-server

+ 28 - 3
src/router/index.js

@@ -118,9 +118,34 @@ export const constantRoutes = [
         meta: { title: '修改生成配置' }
       }
     ]
-  }
-   
- 
+  },
+  {
+    path: '/qw/sopTempe',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: 'updateSopTemp/:id/:type(\\d+)', // 确保 :type 的正则匹配数字
+        component: (resolve) => require(['@/views/qw/sopTemp/updateSopTemp'], resolve),
+        name: 'updateSopTemp',
+        meta: { title: '改动SOP模板', activeMenu: '/qw/addSopTemp' }
+      },
+      {
+        path: 'updateTemp/:id/:type(\\d+)', // 确保 :type 的正则匹配数字
+        component: () => import('@/views/qw/sopTemp/updateTemp'),
+        name: 'updateTemp',
+        meta: { title: '改动SOP模板', activeMenu: '/qw/updateTemp' }
+      },
+      {
+        path: 'updateAiChatTemp/:id/:type(\\d+)', // 确保 :type 的正则匹配数字
+        component: () => import('@/views/qw/sopTemp/updateAiChatTemp'),
+        name: 'updateAiChatTemp',
+        meta: { title: '改动SOP模板', activeMenu: '/qw/addSopTemp' }
+      }
+    ]
+  },
+
+
 ]
 
 export default new Router({

+ 1 - 1
src/utils/cos.js

@@ -3,7 +3,7 @@ import { Message } from 'element-ui';
 import { getTmpSecretKey } from '@/api/common';
 
 const config = {
-    Bucket: 'beliyo-1323137866',
+    Bucket: 'hylj-1323137866',
     Region: 'ap-chongqing',
 };
 

+ 1 - 1
src/views/company/CompanyRedPackageLogs/index.vue

@@ -5,7 +5,7 @@
         <el-select v-model="queryParams.status" clearable>
           <el-option label="待审核" :value="0" />
           <el-option label="已通过" :value="1" />
-          <el-option label="已解决" :value="2" />
+          <el-option label="已拒绝" :value="2" />
         </el-select>
       </el-form-item>
       <el-form-item>

+ 3 - 1
src/views/company/company/index.vue

@@ -81,7 +81,6 @@
       <el-table-column label="联系电话" align="center" prop="companyMobile" />
       <el-table-column label="员工数量" align="center" prop="limitUserCount" />
       <el-table-column label="坐席数量" align="center" prop="voiceCallerNumber" />
-      <el-table-column label="红包余额" align="center" prop="redPackageMoney" />
       <el-table-column label="公司地址" align="center" prop="companyAddress" />
       <el-table-column label="通话API" align="center" prop="voiceApiName" />
       <!-- <el-table-column label="AppId" align="center" prop="appId" />
@@ -259,6 +258,9 @@
         <el-form-item label="备注" prop="remark">
           <el-input v-model="rechargeForm.remark" placeholder="请输入备注" />
         </el-form-item>
+        <el-form-item label="凭证" prop="remark">
+          <image-upload v-model="rechargeForm.imgs" :limit="9" />
+        </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitRechargeForm">确 定</el-button>

+ 21 - 9
src/views/company/companyRecharge/index.vue

@@ -77,15 +77,27 @@
               <el-tag prop="status" v-for="(item, index) in payTypeOptions"   v-if="scope.row.payType==item.dictValue">{{item.dictLabel}}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="提交人" align="center" prop="createUserNickName" width="180">
+      <el-table-column label="提交人" align="center" prop="createUserNickName">
       </el-table-column>
-      <el-table-column label="提交时间" align="center" prop="createTime" width="180">
+      <el-table-column label="提交时间" align="center" prop="createTime" width="160">
       </el-table-column>
-      <el-table-column label="审核人" align="center" prop="auditUserNickName" width="180">
+      <el-table-column label="审核人" align="center" prop="auditUserNickName">
       </el-table-column>
-      <el-table-column label="审核时间" align="center" prop="auditTime" width="180">
+      <el-table-column label="审核时间" align="center" prop="auditTime" width="160">
       </el-table-column>
-      <el-table-column label="支付时间" align="center" prop="payTime" width="180">
+      <el-table-column label="支付时间" align="center" prop="payTime" width="160">
+      </el-table-column>
+      <el-table-column label="凭证照片" align="center" prop="images" >
+        <template slot-scope="scope">
+          <div v-if="scope.row.imgs != null && scope.row.imgs != undefined && scope.row.imgs != ''">
+            <el-image
+              style="width: 50px; height: 50px"
+              :src="scope.row.imgs.split(',')[0]"
+              :preview-src-list="scope.row.imgs.split(',')">
+            </el-image>
+            <p style="margin: 0">({{scope.row.imgs.split(',').length}} 张)</p>
+          </div>
+        </template>
       </el-table-column>
       <el-table-column label="状态" align="center" prop="status" >
         <template slot-scope="scope">
@@ -97,17 +109,17 @@
         <template slot-scope="scope">
           <el-button
             v-if="scope.row.isAudit==0"
-            size="mini" 
+            size="mini"
             type="text"
             icon="el-icon-edit"
             @click="handleAudit(scope.row)"
             v-hasPermi="['company:companyRecharge:audit']"
           >审核</el-button>
-          
+
         </template>
       </el-table-column>
     </el-table>
-    
+
     <pagination
       v-show="total>0"
       :total="total"
@@ -197,7 +209,7 @@ export default {
   created() {
      getCompanyList().then(response => {
       this.companys = response.data;
-       
+
     });
     this.getDicts("comapny_recharge_pay_type").then((response) => {
       this.payTypeOptions = response.data;

+ 262 - 10
src/views/components/course/userCourseCatalogDetails.vue

@@ -28,6 +28,32 @@
           @click="handleAdd"
         >新增目录</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          :disabled="!ids || ids.length <= 0"
+          size="mini"
+          @click="openUpdates"
+        >修改时间</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          size="mini"
+          @click="openAdds"
+        >批量添加</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          size="mini"
+          v-if="isPrivate === 1"
+          @click="updateRedPageckeOpen"
+        >修改红包</el-button>
+      </el-col>
       <el-col :span="1.5">
         <el-button
           type="danger"
@@ -52,6 +78,7 @@
               {{ formatDuration(row.duration) }}
           </template>
       </el-table-column>
+      <el-table-column label="红包金额" align="center" prop="redPacketMoney" v-if="isPrivate === 1"/>
       <el-table-column label="排序" align="center" prop="courseSort" />
       <el-table-column label="上传时间" align="center" prop="createTime" />
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
@@ -93,6 +120,27 @@
         <el-form-item label="课程排序" prop="courseSort">
           <el-input-number v-model="form.courseSort" :min="1" ></el-input-number>
         </el-form-item>
+<!--        <el-form-item label="看课时间" prop="timeRange" v-if="isPrivate === 1">-->
+<!--          <el-time-picker-->
+<!--            is-range-->
+<!--            v-model="form.timeRange"-->
+<!--            range-separator="至"-->
+<!--            start-placeholder="开始时间"-->
+<!--            value-format="HH:mm:ss"-->
+<!--            end-placeholder="结束时间"-->
+<!--            placeholder="选择时间范围">-->
+<!--          </el-time-picker>-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="领取红包时间" prop="lastJoinTime" v-if="isPrivate === 1">-->
+<!--          <el-time-picker-->
+<!--            v-model="form.lastJoinTime"-->
+<!--            :selectableRange="form.timeRange"-->
+<!--            value-format="HH:mm:ss"-->
+<!--            placeholder="选择时间范围">-->
+<!--          </el-time-picker>-->
+<!--          <p style="color: red;margin: 0;font-size: 12px">超过领取红包时间,只允许看课,不允许领取红包</p>-->
+<!--        </el-form-item>-->
+
         <el-form-item label="视频缩略图" prop="thumbnail">
           <el-upload
             v-model="form.thumbnail"
@@ -123,7 +171,7 @@
           append-to-body
         />
 
-        <el-form-item label="课题选择" prop="questionBankId">
+        <el-form-item label="课题选择" prop="questionBankId" v-if="isPrivate === 1">
           <el-button size="small" type="primary" @click="chooseQuestionBank">选取课题</el-button>
           <el-table border width="100%" style="margin-top:5px;"  :data="form.questionBankList">
 
@@ -154,7 +202,7 @@
             </el-table-column>
           </el-table>
         </el-form-item >
-        <el-form-item label="红包金额" prop="redPacketMoney">
+        <el-form-item label="红包金额" prop="redPacketMoney" v-if="isPrivate === 1">
           <el-input-number v-model="form.redPacketMoney" :min="0.1" :max="200" :step="0.1" ></el-input-number>
         </el-form-item>
       </el-form>
@@ -163,22 +211,134 @@
         <el-button @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
+    <el-dialog :title="title" :visible.sync="updateBatchData.open" width="1000px" append-to-body >
+      <el-form ref="form" :model="updateBatchData.form" label-width="110px">
+        <el-form-item label="看课时间" prop="timeRange">
+          <el-time-picker
+            is-range
+            v-model="updateBatchData.form.timeRange"
+            range-separator="至"
+            start-placeholder="开始时间"
+            value-format="HH:mm:ss"
+            end-placeholder="结束时间"
+            placeholder="选择时间范围">
+          </el-time-picker>
+        </el-form-item>
+        <el-form-item label="领取红包时间" prop="lastJoinTime">
+          <el-time-picker
+            v-model="updateBatchData.form.lastJoinTime"
+            :selectableRange="updateBatchData.form.timeRange"
+            value-format="HH:mm:ss"
+            placeholder="选择时间范围">
+          </el-time-picker>
+          <p style="color: red;margin: 0;font-size: 12px">超过领取红包时间,只允许看课,不允许领取红包</p>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="updateBatch">确 定</el-button>
+        <el-button @click="updateBatchData.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
     <el-dialog :title="questionBank.title" :visible.sync="questionBank.open" width="800px" append-to-body >
       <question-bank ref="questionBank" @questionBankResult="questionBankResult" ></question-bank>
     </el-dialog>
+    <el-dialog title="视频库选择" :visible.sync="addBatchData.open" width="900px" append-to-body>
+      <!-- 搜索条件 -->
+      <el-form :inline="true" :model="addBatchData.queryParams" class="library-search">
+        <el-form-item label="素材名称">
+          <el-input
+            v-model="addBatchData.queryParams.resourceName"
+            placeholder="请输入素材名称"
+            clearable
+            size="small"
+            @keyup.enter.native="resourceList"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="resourceList">搜索</el-button>
+        </el-form-item>
+      </el-form>
+
+      <!-- 视频列表 -->
+      <el-table v-loading="addBatchData.loading" :data="addBatchData.list" @selection-change="handVideoleSelectionChange" height="400px">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="素材名称" align="center" prop="resourceName" />
+        <el-table-column label="文件名称" align="center" prop="fileName" />
+        <el-table-column label="缩略图" align="center">
+          <template slot-scope="scope">
+            <el-popover
+              placement="right"
+              title=""
+              trigger="hover"
+            >
+              <img alt="" slot="reference" :src="scope.row.thumbnail" style="width: 80px; height: 50px" />
+              <img alt="" :src="scope.row.thumbnail" style="max-width: 150px;" />
+            </el-popover>
+          </template>
+        </el-table-column>
+        <el-table-column label="视频时长" align="center">
+          <template slot-scope="scope">
+            <span>{{ formatDuration(scope.row.duration) }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <pagination
+        v-show="addBatchData.total>0"
+        :total="addBatchData.total"
+        :page.sync="addBatchData.queryParams.pageNum"
+        :limit.sync="addBatchData.queryParams.pageSize"
+        @pagination="resourceList"
+      />
+
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="batchVideoSave">确 定</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog title="章节红包" :visible.sync="redData.open" width="900px" append-to-body>
+      <el-table border v-loading="redData.loading" :data="redData.list" height="600px">
+        <el-table-column label="小节名称" align="center" show-overflow-tooltip prop="title" />
+        <el-table-column label="视频文件名称" align="center" show-overflow-tooltip  prop="fileName" >
+        </el-table-column>
+        <el-table-column label="视频时长" align="center" prop="duration">
+          <template slot-scope="{ row }">
+            {{ formatDuration(row.duration) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="红包金额" align="center" prop="redPacketMoney">
+          <template slot-scope="scope">
+            <el-input class="el-input" v-model="scope.row.redPacketMoney" />
+          </template>
+        </el-table-column>
+        <el-table-column label="排序" align="center" prop="courseSort" />
+        <el-table-column label="上传时间" align="center" prop="createTime" />
+      </el-table>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="batchRedSave">确 定</el-button>
+      </div>
+    </el-dialog>
 
   </div>
 </template>
 
 <script>
-import { getSort,getVideoListByCourseId,delUserCourseVideo,getUserCourseVideo,addUserCourseVideo,updateUserCourseVideo } from "@/api/course/userCourseVideo";
-import {getSignature,uploadHuaWeiVod,uploadHuaWeiObs} from "@/api/common"
-import  QuestionBank from  "@/views/course/courseQuestionBank/QuestionBank.vue";
-import TcVod from 'vod-js-sdk-v6'
-import request from '@/utils/request'
-import {uploadObject} from "@/utils/cos";
+import {
+  addUserCourseVideo,
+  delUserCourseVideo,
+  getSort,
+  getUserCourseVideo,
+  getVideoListByCourseId,
+  updates,
+  batchSaveVideo,
+  batchUpdateRed,
+  updateUserCourseVideo
+} from "@/api/course/userCourseVideo";
+import QuestionBank from "@/views/course/courseQuestionBank/QuestionBank.vue";
 import VideoUpload from "@/components/VideoUpload/index.vue";
-  export default {
+import { listVideoResource } from '@/api/course/videoResource';
+
+export default {
     name: "userCourseCatalog",
     components: {VideoUpload,QuestionBank},
     data() {
@@ -222,12 +382,35 @@ import VideoUpload from "@/components/VideoUpload/index.vue";
         courseName:null,
         userCourseVideoList:[],
         total: 0,
+        redData:{
+          queryParams: {
+            pageNum: 1,
+            pageSize: 99999,
+            courseId:null,
+          },
+          list: [],
+          open: false,
+          loading:true,
+          form:{
+            }
+        },
         queryParams: {
           pageNum: 1,
           pageSize: 10,
           courseId:null,
           title:null
         },
+        addBatchData: {
+          open: false,
+          loading: true,
+          form: {},
+          select: [],
+          total:0,
+          queryParams: {
+            pageNum: 1,
+            pageSize: 10,
+          },
+        },
         // 显示搜索条件
         showSearch: true,
         // 遮罩层
@@ -242,6 +425,10 @@ import VideoUpload from "@/components/VideoUpload/index.vue";
         multiple: true,
           // 表单参数
         form: {},
+        updateBatchData: {
+          open: false,
+          form:{}
+        },
         // 表单校验
         rules: {
           title: [
@@ -451,10 +638,14 @@ import VideoUpload from "@/components/VideoUpload/index.vue";
       },
       // 多选框选中数据
       handleSelectionChange(selection) {
-        this.ids = selection.map(item => item.courseId)
+        this.ids = selection.map(item => item.videoId)
         this.single = selection.length!==1
         this.multiple = !selection.length
       },
+      // 多选框选中数据
+      handVideoleSelectionChange(selection) {
+        this.addBatchData.select = selection.map(item => item.id);
+      },
       handleAdd(){
         this.reset();
         this.form.courseId = this.courseId;
@@ -483,6 +674,10 @@ import VideoUpload from "@/components/VideoUpload/index.vue";
           if (this.form.packageJson!=null){
             this.packageList = JSON.parse(this.form.packageJson);
           }
+
+          if(response.data.viewStartTime != null && response.data.viewEndTime != null){
+            this.form.timeRange = [response.data.viewStartTime, response.data.viewEndTime]
+          }
           setTimeout(() => {
             this.$refs.videoUpload.resetUpload();
           }, 500);
@@ -504,6 +699,10 @@ import VideoUpload from "@/components/VideoUpload/index.vue";
               });
               return
             }
+            if(this.form.timeRange != null && this.form.timeRange.length === 2){
+              this.form.viewStartTime = this.form.timeRange[0];
+              this.form.viewEndTime = this.form.timeRange[1];
+            }
             if(this.form.duration==null){
               this.$message({
                 message: '未识别到视频时长请稍等。。。',
@@ -540,6 +739,23 @@ import VideoUpload from "@/components/VideoUpload/index.vue";
           }
         });
       },
+      openUpdates(){
+        this.updateBatchData.form = {};
+        this.updateBatchData.open = true;
+      },
+      /** 提交按钮 */
+      updateBatch() {
+        this.updateBatchData.form.ids = this.ids;
+        if(this.updateBatchData.form.timeRange != null && this.updateBatchData.form.timeRange.length === 2){
+          this.updateBatchData.form.viewStartTime = this.updateBatchData.form.timeRange[0];
+          this.updateBatchData.form.viewEndTime = this.updateBatchData.form.timeRange[1];
+        }
+          updates(this.updateBatchData.form).then(response => {
+            this.msgSuccess("修改成功");
+            this.updateBatchData.open = false;
+            this.getList();
+          });
+      },
       /** 删除按钮操作 */
       handleDelete(row) {
         const videoIds = row.videoId || this.ids;
@@ -554,6 +770,42 @@ import VideoUpload from "@/components/VideoUpload/index.vue";
             this.msgSuccess("删除成功");
           }).catch(() => {});
       },
+      openAdds(){
+        this.addBatchData.open = true;
+        this.addBatchData.form = {
+          courseId: this.courseId,
+        };
+        this.resourceList();
+      },
+      resourceList(){
+        this.addBatchData.loading = true;
+        listVideoResource(this.addBatchData.queryParams).then(response => {
+          this.addBatchData.loading = false;
+          this.addBatchData.list = response.rows;
+          this.addBatchData.total = response.total;
+        });
+      },
+      batchVideoSave(){
+        this.addBatchData.form.ids = this.addBatchData.select;
+        batchSaveVideo(this.addBatchData.form).then(response => {
+          this.addBatchData.open = false;
+          this.getList();
+        })
+      },
+      updateRedPageckeOpen(){
+        this.redData.open = true;
+        this.redData.loading = true;
+        this.redData.queryParams.courseId = this.courseId;
+        getVideoListByCourseId(this.redData.queryParams).then(response => {
+          this.redData.list = response.rows;
+          this.redData.loading = false;
+        });
+      },
+      batchRedSave(){
+        batchUpdateRed(this.redData.list).then(response => {
+          this.redData.open = false;
+        })
+      },
     }
   }
 </script>

+ 916 - 0
src/views/course/courseFinishTemp/index.vue

@@ -0,0 +1,916 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="销售公司" prop="companyId">
+        <el-select filterable  v-model="queryParams.companyId" clearable placeholder="请选择公司名" size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
+      </el-form-item>
+      <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="courseId">
+        <el-input
+          v-model="queryParams.courseId"
+          placeholder="请输入课程id"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId">
+        <el-input
+          v-model="queryParams.videoId"
+          placeholder="请输入小节视频id"
+          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-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['courseFinishTemp:course: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="['courseFinishTemp:course: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="['courseFinishTemp:course: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="['courseFinishTemp:course:export']"
+        >导出
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="courseFinishTempList" @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="status">
+        <template slot-scope="scope">
+          <el-tag v-for="item in companys" v-if="item.companyId == scope.row.companyId" >{{item.companyName}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="模板名称" align="center" prop="name"/>
+      <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="courseName"/>
+      <el-table-column label="小节名称" align="center" prop="videoName"/>
+      <el-table-column label="创建时间" align="center" prop="createTime"/>
+      <el-table-column label="修改时间" align="center" prop="updateTime"/>
+      <el-table-column label="全选销售" align="center" prop="isAllCompanyUser">
+        <template slot-scope="scope">
+          <dict-tag :options="allowSelect" :value="scope.row.isAllCompanyUser"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['courseFinishTemp:course:edit']"
+          >修改
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-s-promotion"
+            @click="handleSelectDetails(scope.row)"
+            v-hasPermi="['courseFinishTemp:course:query']"
+          >详情
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['courseFinishTemp:course: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="1000px" 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="销售公司" prop="companyId">
+          <el-select filterable  v-model="form.companyId" placeholder="请选择公司" size="small" @change="initCompanyUserList">
+            <el-option
+              v-for="item in companys"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="类型">
+          <el-radio-group v-model="form.type">
+            <el-radio :label="1">个人消息</el-radio>
+            <el-radio :label="2">群发消息</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"
+              :key="dict.dictValue"
+              :label="parseInt(dict.dictValue)"
+            >{{ dict.dictLabel }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="选择课程">
+          <el-select v-model="form.courseId" placeholder="请选择课程" style=" margin-right: 10px;" size="mini"
+                     @change="courseChange()">
+            <el-option
+              v-for="dict in courseList"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="parseInt(dict.dictValue)"
+            />
+          </el-select>
+          <el-select v-model="form.videoId" placeholder="请选择小节" size="mini" style=" margin-right: 10px;">
+            <el-option
+              v-for="dict in videoList"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="parseInt(dict.dictValue)"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="规则" prop="setting">
+          <div v-for="(item, index) in setting" :key="index"
+               style="background-color: #fdfdfd; border: 1px solid #e6e6e6; margin-bottom: 20px;">
+            <el-row>
+              <el-col :span="22">
+                <el-form :model="item" label-width="70px">
+                  <el-form-item label="内容类别" style="margin: 2%">
+                    <el-radio-group v-model="item.contentType">
+                      <el-radio :label="item.dictValue" v-for="item in sysQwSopAiContentType">{{ item.dictLabel }}
+                      </el-radio>
+                    </el-radio-group>
+                  </el-form-item>
+                  <el-form-item label="内容" style="margin-bottom: 2%">
+                    <el-input v-if="item.contentType == 1 " v-model="item.value" type="textarea" :rows="3"
+                              placeholder="内容" style="width: 90%;margin-top: 10px;"/>
+
+                    <ImageUpload v-if="item.contentType == 2 " v-model="item.imgUrl" type="image" :num="1" :width="150"
+                                 :height="150"/>
+
+                    <div v-if="item.contentType == 3 ">
+                      <el-card class="box-card">
+                        <el-form-item label="链接标题:" label-width="100px">
+                          <el-input v-model="item.linkTitle" placeholder="请输入链接标题" style="width: 90%;"/>
+                        </el-form-item>
+                        <el-form-item label="链接描述:" label-width="100px">
+                          <el-input type="textarea" :rows="3" v-model="item.linkDescribe" placeholder="请输入链接描述"
+                                    style="width: 90%;margin-top: 1%;"/>
+                        </el-form-item>
+                        <el-form-item label="链接封面:" label-width="100px">
+                          <ImageUpload v-model="item.linkImageUrl" type="image" :num="1" :file-size="2" :width="150"
+                                       :height="150" style="margin-top: 1%;"/>
+                        </el-form-item>
+                        <el-form-item label="链接地址:" label-width="100px">
+                          <el-input v-model="item.linkUrl" placeholder="请输入链接地址" style="width: 90%;"/>
+                        </el-form-item>
+                      </el-card>
+                    </div>
+                    <div v-if="item.contentType == 4">
+
+                    </div>
+                    <div v-if="item.contentType == 5 ">
+
+                      <el-form-item label="上传文件:" prop="fileUrl" label-width="100px">
+                        <el-upload
+                          v-model="item.fileUrl"
+                          class="avatar-uploader"
+                          :action="uploadUrl"
+                          :show-file-list="false"
+                          :on-success="(res, file) => handleAvatarSuccessFile(res, file, item)"
+                          :before-upload="beforeAvatarUploadFile">
+                          <i class="el-icon-plus avatar-uploader-icon"></i>
+                        </el-upload>
+                        <el-link v-if="item.fileUrl" type="primary" :href="downloadUrl(item.fileUrl)" download>
+                          {{ item.fileUrl }}
+                        </el-link>
+                      </el-form-item>
+
+                    </div>
+
+                    <div v-if="item.contentType == 6 ">
+                      <el-form-item label="上传视频:" prop="videoUrl" label-width="100px">
+                        <el-upload
+                          v-model="item.videoUrl"
+                          class="avatar-uploader"
+                          :action="uploadUrl"
+                          :show-file-list="false"
+                          :on-success="(res, file) => handleAvatarSuccessVideo(res, file, item)"
+                          :before-upload="beforeAvatarUploadVideo">
+                          <i class="el-icon-plus avatar-uploader-icon"></i>
+                        </el-upload>
+                        <video v-if="item.videoUrl"
+                               :src="item.videoUrl"
+                               controls style="width: 200px;height: 100px">
+                        </video>
+                      </el-form-item>
+                    </div>
+                    <div v-if="item.contentType == 7 ">
+                      <el-input
+                        v-model="item.value"
+                        type="textarea" :rows="3" maxlength="66" show-word-limit
+                        placeholder="输入要转为语音的内容" style="width: 90%;margin-top: 10px;"
+                        @input="handleInputVideoText(item.value,item)"/>
+                    </div>
+                    <div v-if="item.contentType == 8">
+
+                    </div>
+
+                  </el-form-item>
+                </el-form>
+              </el-col>
+              <el-col :span="1" :offset="1">
+                <i class="el-icon-delete" @click="delSetList(index, 0)" style="margin-top: 20px;"
+                   v-if="setting.length>1 && formType==1"></i>
+              </el-col>
+            </el-row>
+          </div>
+          <el-link type="primary" class="el-icon-plus" :underline="false" @click='addSetList(0)' v-if="formType==1">
+            添加内容
+          </el-link>
+        </el-form-item>
+        <el-form-item label="群聊恭喜规则" prop="setting">
+          <div v-for="(item, index) in chatSetting" :key="index"
+               style="background-color: #fdfdfd; border: 1px solid #e6e6e6; margin-bottom: 20px;">
+            <el-row>
+              <el-col :span="22">
+                <el-form :model="item" label-width="70px">
+                  <el-form-item label="内容类别" style="margin: 2%">
+                    <el-radio-group v-model="item.contentType">
+                      <el-radio :label="item.dictValue" v-for="item in sysQwSopAiContentType">{{ item.dictLabel }}
+                      </el-radio>
+                    </el-radio-group>
+                  </el-form-item>
+                  <el-form-item label="内容" style="margin-bottom: 2%">
+                    <el-input v-if="item.contentType == 1 " v-model="item.value" type="textarea" :rows="3"
+                              placeholder="内容" style="width: 90%;margin-top: 10px;"/>
+
+                    <ImageUpload v-if="item.contentType == 2 " v-model="item.imgUrl" type="image" :num="1" :width="150"
+                                 :height="150"/>
+
+                    <div v-if="item.contentType == 3 ">
+                      <el-card class="box-card">
+                        <el-form-item label="链接标题:" label-width="100px">
+                          <el-input v-model="item.linkTitle" placeholder="请输入链接标题" style="width: 90%;"/>
+                        </el-form-item>
+                        <el-form-item label="链接描述:" label-width="100px">
+                          <el-input type="textarea" :rows="3" v-model="item.linkDescribe" placeholder="请输入链接描述"
+                                    style="width: 90%;margin-top: 1%;"/>
+                        </el-form-item>
+                        <el-form-item label="链接封面:" label-width="100px">
+                          <ImageUpload v-model="item.linkImageUrl" type="image" :num="1" :file-size="2" :width="150"
+                                       :height="150" style="margin-top: 1%;"/>
+                        </el-form-item>
+                        <el-form-item label="链接地址:" label-width="100px">
+                          <el-input v-model="item.linkUrl" placeholder="请输入链接地址" style="width: 90%;"/>
+                        </el-form-item>
+                      </el-card>
+                    </div>
+                    <div v-if="item.contentType == 4">
+
+                    </div>
+                    <div v-if="item.contentType == 5 ">
+
+                      <el-form-item label="上传文件:" prop="fileUrl" label-width="100px">
+                        <el-upload
+                          v-model="item.fileUrl"
+                          class="avatar-uploader"
+                          :action="uploadUrl"
+                          :show-file-list="false"
+                          :on-success="(res, file) => handleAvatarSuccessFile(res, file, item)"
+                          :before-upload="beforeAvatarUploadFile">
+                          <i class="el-icon-plus avatar-uploader-icon"></i>
+                        </el-upload>
+                        <el-link v-if="item.fileUrl" type="primary" :href="downloadUrl(item.fileUrl)" download>
+                          {{ item.fileUrl }}
+                        </el-link>
+                      </el-form-item>
+
+                    </div>
+
+                    <div v-if="item.contentType == 6 ">
+                      <el-form-item label="上传视频:" prop="videoUrl" label-width="100px">
+                        <el-upload
+                          v-model="item.videoUrl"
+                          class="avatar-uploader"
+                          :action="uploadUrl"
+                          :show-file-list="false"
+                          :on-success="(res, file) => handleAvatarSuccessVideo(res, file, item)"
+                          :before-upload="beforeAvatarUploadVideo">
+                          <i class="el-icon-plus avatar-uploader-icon"></i>
+                        </el-upload>
+                        <video v-if="item.videoUrl"
+                               :src="item.videoUrl"
+                               controls style="width: 200px;height: 100px">
+                        </video>
+                      </el-form-item>
+                    </div>
+                    <div v-if="item.contentType == 7 ">
+                      <el-input
+                        v-model="item.value"
+                        type="textarea" :rows="3" maxlength="66" show-word-limit
+                        placeholder="输入要转为语音的内容" style="width: 90%;margin-top: 10px;"
+                        @input="handleInputVideoText(item.value,item)"/>
+                    </div>
+                    <div v-if="item.contentType == 8">
+
+                    </div>
+
+                  </el-form-item>
+                </el-form>
+              </el-col>
+              <el-col :span="1" :offset="1">
+                <i class="el-icon-delete" @click="delSetList(index,1)" style="margin-top: 20px;"
+                   v-if="setting.length>1 && formType==1"></i>
+              </el-col>
+            </el-row>
+          </div>
+          <el-link type="primary" class="el-icon-plus" :underline="false" @click='addSetList(1)' v-if="formType==1">
+            添加内容
+          </el-link>
+        </el-form-item>
+        <el-form-item label="全选销售" prop="isAllCompanyUser">
+          <el-switch
+            v-model="form.isAllCompanyUser"
+            active-color="#13ce66"
+            inactive-color="#ff4949"
+            :active-value="1"
+            :inactive-value="2">
+          </el-switch>
+          <span v-if="form.isAllCompanyUser == '1'" style="margin-left: 10px;color: #13ce66">是</span>
+          <span v-else style="margin-left: 10px;color: #ff4949">否</span>
+        </el-form-item>
+        <el-form-item label="所属销售" prop="companyUserIds">
+          <el-select v-model="companyUserIds" remote multiple placeholder="请选择" filterable style="width: 100%;" v-loading="userLoading">
+            <el-option
+              v-for="dict in userList"
+              :key="dict.userId"
+              :label="dict.nickName"
+              :value="dict.userId.toString()">
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer" v-if="formType==1">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  listCourseFinishTemp,
+  getCourseFinishTemp,
+  addCourseFinishTemp,
+  updateCourseFinishTemp,
+  delCourseFinishTemp,
+  exportCourseFinishTemp
+} from "@/api/course/courseFinishTemp";
+import {getAllUserlist} from '@/api/company/companyUser'
+import {courseList, videoList} from '@/api/qw/sop'
+import ImageUpload from "@/views/qw/sop/ImageUpload.vue";
+import { getCompanyList } from "@/api/company/company";
+
+export default {
+  name: "CourseFinishTemp",
+  components: {ImageUpload},
+  data() {
+    return {
+      //上传语音的遮罩层
+      voiceLoading: false,
+      uploadUrl: process.env.VUE_APP_BASE_API + "/common/uploadOSS2",
+      uploadUrlByVoice: process.env.VUE_APP_BASE_API + "/common/uploadOSSByHOOKVoice",
+      companyUserIds: [],
+      userList: [],
+      // 状态字典
+      statusOptions: [],
+      allowSelect: [],
+      courseList: [],
+      videoList: [],
+      // 遮罩层
+      loading: true,
+      userLoading: false,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      companys: [],
+      formType: 1,
+      // 总条数
+      total: 0,
+      // 完课模板表格数据
+      courseFinishTempList: [],
+      //插件版
+      sysQwSopAiContentType: [],
+
+      sysFsSopWatchStatus: [],
+
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        status: null,
+        setting: null,
+        chatSetting: null,
+        companyId: null,
+        courseId: null,
+        videoId: null,
+        companyUserIds: null,
+        isDel: null
+      },
+      // 模板表格数据
+      setting: [],
+      chatSetting: [],
+      // 表单参数
+      form: {
+        setting: null,
+        chatSetting: null,
+        videoIdSet: null,
+        courseIdSet: null,
+      },
+      // 表单校验
+      rules: {}
+    };
+  },
+  created() {
+    this.getList();
+
+    getCompanyList().then(response => {
+      this.companys = response.data;
+    });
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+
+    //复用一下
+    this.getDicts("sys_qw_allow_select").then(response => {
+      this.allowSelect = response.data;
+    });
+
+    this.getDicts("sys_fs_sop_watch_status").then(response => {
+      this.sysFsSopWatchStatus = response.data;
+    });
+
+    this.getDicts("sys_qwSopAi_contentType").then(response => {
+      this.sysQwSopAiContentType = response.data;
+    });
+    courseList().then(response => {
+      this.courseList = response.list;
+    });
+  },
+  methods: {
+    courseChange() {
+
+      videoList(this.form.courseId).then(response => {
+        this.videoList = response.list;
+      });
+    },
+    /** 查询完课模板列表 */
+    getList() {
+      this.loading = true;
+      listCourseFinishTemp(this.queryParams).then(response => {
+        this.courseFinishTempList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    handleAvatarSuccessFile(res, file, item) {
+      if (res.code === 200) {
+        // 使用 $set 确保响应式更新
+        this.$set(item, 'fileUrl', res.url);
+      } else {
+        this.msgError(res.msg);
+      }
+    },
+
+    beforeAvatarUploadFile(file) {
+      const isLt1M = file.size / 1024 / 1024 < 10;
+      if (!isLt1M) {
+        this.$message.error('上传大小不能超过 10MB!');
+      }
+      return isLt1M;
+    },
+    //下载文件
+    downloadUrl(materialUrl) {
+      // 直接返回文件 URL
+      return materialUrl;
+    },
+    handleInputVideoText(value, content) {
+      // 允许的字符:中文、英文(大小写)、数字和指定标点符号(,。!?)
+      const regex = /^[\u4e00-\u9fa5,。!?,!?]+$/;
+
+      // 删除不符合条件的字符
+      const filteredValue = value.split('').filter(char => regex.test(char)).join('');
+
+      this.$set(content, 'value', filteredValue);
+
+    },
+    handleAvatarSuccessVideo(res, file, item) {
+      if (res.code == 200) {
+        // 使用 $set 确保响应式更新
+        this.$set(item, 'videoUrl', res.url);
+      } else {
+        this.msgError(res.msg);
+      }
+    },
+
+    beforeAvatarUploadVideo(file) {
+      const isLt30M = file.size / 1024 / 1024 < 10;
+      const isMP4 = file.type === 'video/mp4';
+
+      if (!isMP4) {
+        this.$message.error('仅支持上传 MP4 格式的视频文件!');
+        return false;
+      }
+
+      if (!isLt30M) {
+        this.$message.error('上传大小不能超过 10MB!');
+        return false;
+      }
+
+      return true;
+    },
+
+    handleAvatarSuccessVoice(res, file, item) {
+      if (res.code == 200) {
+
+        // 创建 Audio 对象加载音频
+        const audio = new Audio(res.mp3Url);
+        audio.addEventListener('loadedmetadata', () => {
+          // 获取音频时长
+          this.$set(item, 'voiceDuration', Math.ceil(audio.duration));
+        });
+        // 使用 $set 确保响应式更新
+        this.$set(item, 'voiceUrl', res.silkUrl);
+        this.$set(item, 'mp3Url', res.mp3Url);
+      } else {
+        this.msgError(res.msg);
+      }
+      this.voiceLoading = false;
+    },
+    delSetList(index, type) {
+      if (type == 0) {
+        this.setting.splice(index, 1)
+      } else {
+        this.chatSetting.splice(index, 1)
+      }
+    },
+    beforeAvatarUploadVoice(file) {
+      return new Promise((resolve, reject) => {
+        const isLt10M = file.size / 1024 / 1024 < 10; // 假设语音文件大小限制为10MB
+        const isVoiceType = ['audio/mp3', 'audio/mpeg', 'audio/wav', 'audio/x-wav'].includes(file.type);
+
+        if (!isVoiceType) {
+          this.$message.error('仅支持上传 MP3, WAV, X-WAV 格式的语音文件!');
+          return reject(false); // 不允许继续上传
+        }
+
+        if (!isLt10M) {
+          this.$message.error('上传大小不能超过 10MB!');
+          return reject(false); // 不允许继续上传
+        }
+
+        // 使用 FileReader 读取文件
+        const reader = new FileReader();
+        reader.onload = (event) => {
+          const audio = new Audio(event.target.result);
+          audio.addEventListener('loadedmetadata', () => {
+            // 获取时长并保存
+            if (Math.ceil(audio.duration) > 30) {
+              this.$message.error('音频时长不能超过30秒!');
+              this.voiceLoading = false;
+              return reject(false); // 不允许继续上传
+            }
+            resolve(true); // 允许上传
+          });
+        };
+
+        reader.onerror = () => {
+          this.$message.error('无法读取音频文件!请上传正确的音频文件');
+          reject(false); // 不允许继续上传
+        };
+        this.voiceLoading = true;
+        reader.readAsDataURL(file); // 开始读取文件
+      });
+    },
+
+    addSetList(type) {
+      const newSetting = {
+        contentType: '1',
+        value: '',
+      };
+      // 将新设置项添加到 content.setting 数组中
+      if (type == 0) {
+        this.setting.push(newSetting);
+      } else {
+        this.chatSetting.push(newSetting);
+      }
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        type: 1,
+        status: 1,
+        setting: [],
+        chatSetting: [],
+        companyId: null,
+        createBy: null,
+        createTime: null,
+        courseId: null,
+        videoId: null,
+        companyUserIds: null,
+        updateTime: null,
+        isDel: null,
+        isAllCompanyUser: null,
+      };
+      this.companyUserIds = []
+      this.setting = []
+      this.chatSetting = []
+      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 = "添加完课模板";
+    },
+
+    /**
+     * 查看完课模板
+     */
+    handleSelectDetails(row) {
+
+      this.reset();
+      const id = row.id || this.ids
+      getCourseFinishTemp(id).then(response => {
+        this.form = JSON.parse(JSON.stringify(response.data));
+        this.initCompanyUserList();
+
+        this.setting = JSON.parse(this.form.setting)
+        this.chatSetting = JSON.parse(this.form.chatSetting)
+        if (response.data.companyUserIds != null) {
+          this.companyUserIds = response.data.companyUserIds.split(",");
+        }
+
+        videoList(this.form.courseId).then(response => {
+          this.videoList = response.list;
+        });
+
+        this.open = true;
+        this.title = "查看完课模板";
+        this.formType = 2;
+
+      });
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getCourseFinishTemp(id).then(response => {
+        this.form = response.data;
+        this.setting = JSON.parse(this.form.setting)
+        this.chatSetting = JSON.parse(this.form.chatSetting)
+        if (response.data.companyUserIds != null) {
+          this.companyUserIds = this.form.companyUserIds.split(",");
+        }
+        videoList(this.form.courseId).then(response => {
+
+          this.videoList = response.list;
+        });
+        this.open = true;
+        this.title = "修改完课模板";
+        this.initCompanyUserList();
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+
+          if (this.form.isAllCompanyUser == null) {
+            this.form.isAllCompanyUser = 2;
+          }
+          this.form.companyUserIds = this.companyUserIds.toString()
+
+          this.form.setting = JSON.stringify(this.setting)
+          this.form.chatSetting = JSON.stringify(this.chatSetting)
+
+          if (this.setting.length <= 0) {
+            return this.$message("请添加规则")
+          }
+          for (let i = 0; i < this.setting.length; i++) {
+            if (this.setting[i].contentType == 1 && (this.setting[i].value == null || this.setting[i].value == "")) {
+              return this.$message.error("内容不能为空")
+            }
+            if (this.setting[i].contentType == 2 && (this.setting[i].imgUrl == null || this.setting[i].imgUrl == "")) {
+              return this.$message.error("图片不能为空")
+            }
+            if (this.setting[i].contentType == 3 && (this.setting[i].linkTitle == null || this.setting[i].linkTitle == "")) {
+              return this.$message.error("链接标题不能为空")
+            }
+            if (this.setting[i].contentType == 3 && (this.setting[i].linkDescribe == null || this.setting[i].linkDescribe == "")) {
+              return this.$message.error("链接描述不能为空")
+            }
+            if (this.setting[i].contentType == 3 && (this.setting[i].linkImageUrl == null || this.setting[i].linkImageUrl == "")) {
+              return this.$message.error("链接图片不能为空")
+            }
+            if (this.setting[i].contentType == 3 && this.setting[i].type == 1 && (this.setting[i].linkUrl == null || this.setting[i].linkUrl == "")) {
+              return this.$message.error("链接地址不能为空")
+            }
+            if (this.setting[i].contentType == 5 && (this.setting[i].fileUrl == null || this.setting[i].fileUrl == "")) {
+              return this.$message.error("文件不能为空")
+            }
+            if (this.setting[i].contentType == 6 && (this.setting[i].videoUrl == null || this.setting[i].videoUrl == "")) {
+              return this.$message.error("视频不能为空")
+            }
+            if (this.setting[i].contentType == 7 && (this.setting[i].value == null || this.setting[i].value == "")) {
+              return this.$message.error("语音不能为空")
+            }
+          }
+
+          if (this.form.id != null) {
+            updateCourseFinishTemp(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+              this.setting = [];
+              this.chatSetting = [];
+            });
+          } else {
+            addCourseFinishTemp(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.setting = [];
+              this.chatSetting = [];
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除完课模板编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delCourseFinishTemp(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+    },
+    initCompanyUserList(){
+      this.form.companyUserIds = [];
+      this.userLoading = true;
+      getAllUserlist({companyId: this.form.companyId}).then(response => {
+        this.userLoading = false;
+        this.userList = response.data;
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有完课模板数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportCourseFinishTemp(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+      });
+    }
+  }
+};
+</script>

+ 148 - 22
src/views/course/courseQuestionBank/index.vue

@@ -30,15 +30,25 @@
         </el-select>
       </el-form-item>
 	  <el-form-item label="题目类别" prop="questionType">
-	    <el-select v-model="queryParams.questionType" placeholder="请选择类别" clearable size="small">
+	    <el-select v-model="queryParams.questionType" ref="typeSelect" placeholder="请选择类别" @change="changeCateType" clearable size="small">
 	      <el-option
-	        v-for="dict in questionTypeOptions"
-	        :key="dict.dictValue"
-	        :label="dict.dictLabel"
-	        :value="dict.dictValue"
-	      />
+          v-for="item in questionRootTypeOptions"
+          :key="item.dictValue"
+          :label="item.dictLabel"
+          :value="item.dictValue"
+          />
 	    </el-select>
 	  </el-form-item>
+    <el-form-item label="题目子类别" prop="questionSubType" label-width="100px">
+      <el-select v-model="queryParams.questionSubType" ref="typeSelect" placeholder="请选择子类别" clearable size="small">
+        <el-option
+          v-for="item in questionSubTypeOptions"
+          :key="item.dictValue"
+          :label="item.dictLabel"
+          :value="item.dictValue"
+        />
+      </el-select>
+    </el-form-item>
 
 
 <!--      <el-form-item label="状态" prop="status">-->
@@ -101,6 +111,16 @@
           v-hasPermi="['course:courseQuestionBank:export']"
         >导出</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="info"
+          icon="el-icon-upload2"
+          size="mini"
+          @click="handleImport"
+          v-hasPermi="['course:courseQuestionBank:import']"
+        >导入</el-button>
+      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
@@ -114,9 +134,14 @@
       </el-table-column>
 	  <el-table-column label="题目类别" align="center" prop="questionType">
 	    <template slot-scope="scope">
-	      <dict-tag :options="questionTypeOptions" :value="scope.row.questionType"/>
+        <el-tag v-if="scope.row.questionType">{{ getCategoryName(scope.row.questionType) }}</el-tag>
 	    </template>
 	  </el-table-column>
+    <el-table-column label="题目子类别" align="center" prop="questionType">
+      <template slot-scope="scope">
+        <el-tag v-if="scope.row.questionSubType">{{ getCategoryName(scope.row.questionSubType) }}</el-tag>
+      </template>
+    </el-table-column>
       <el-table-column label="状态" align="center" prop="status">
         <template slot-scope="scope">
           <dict-tag :options="statusOptions" :value="scope.row.status"/>
@@ -172,14 +197,22 @@
           </el-select>
         </el-form-item>
 		<el-form-item label="题目类别 " prop="questionType">
-		  <el-select v-model="form.questionType" placeholder="请选择题目类别" >
-		    <el-option
-		      v-for="dict in questionTypeOptions"
-		      :key="dict.dictValue"
-		      :label="dict.dictLabel"
-		      :value="parseInt(dict.dictValue)"
-		    ></el-option>
+		  <el-select v-model="form.questionType" clearable placeholder="请选择题目类别" @change="changeCateType">
+        <el-option
+          v-for="item in questionRootTypeOptions"
+          :key="item.dictValue"
+          :label="item.dictLabel"
+          :value="item.dictValue"
+        />
 		  </el-select>
+      <el-select v-model="form.questionSubType" clearable placeholder="请选择题目子类别" >
+        <el-option
+          v-for="item in questionSubTypeOptions"
+          :key="item.dictValue"
+          :label="item.dictLabel"
+          :value="item.dictValue"
+        />
+      </el-select>
 		</el-form-item>
         <el-form-item label="状态">
           <el-radio-group v-model="form.status">
@@ -252,11 +285,41 @@
         <el-button @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
+
+    <!-- 导入 -->
+    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
+      <el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">
+          将文件拖到此处,或
+          <em>点击上传</em>
+        </div>
+        <div class="el-upload__tip" slot="tip">
+          <el-link type="info" style="font-size:12px" @click="importTemplate">下载模板</el-link>
+        </div>
+        <div class="el-upload__tip" style="color:red" slot="tip">提示:仅允许导入“xls”或“xlsx”格式文件!</div>
+      </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitFileForm">确 定</el-button>
+        <el-button @click="upload.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog title="导入结果" :close-on-press-escape="false" :close-on-click-modal="false" :visible.sync="importMsgOpen" width="500px" append-to-body>
+      <div class="import-msg" v-html="importMsg">
+      </div>
+    </el-dialog>
+
   </div>
 </template>
 
 <script>
-import { listCourseQuestionBank, getCourseQuestionBank, delCourseQuestionBank, addCourseQuestionBank, updateCourseQuestionBank, exportCourseQuestionBank } from "@/api/course/courseQuestionBank";
+import { listCourseQuestionBank, getCourseQuestionBank, delCourseQuestionBank, addCourseQuestionBank, updateCourseQuestionBank, exportCourseQuestionBank, importTemplate } from "@/api/course/courseQuestionBank";
+import { getToken } from "@/utils/auth";
+import {
+  listUserCourseCategory,
+  getCatePidList,
+  getCateListByPid
+} from '@/api/course/userCourseCategory'
 
 export default {
   name: "CourseQuestionBank",
@@ -299,6 +362,8 @@ export default {
       // 题库表格数据
       courseQuestionBankList: [],
 	  questionTypeOptions: [],
+      questionRootTypeOptions: [],
+	    questionSubTypeOptions: [],
       // 弹出层标题
       title: "",
       // 是否显示弹出层
@@ -313,6 +378,7 @@ export default {
         status: null,
         question: null,
 		    questionType:null,
+        questionSubType: null,
         answer: null,
       },
       // 表单参数
@@ -325,6 +391,21 @@ export default {
         status: [{ required: true, message: "不能为空", trigger: "blur" }],
         question: [{ required: true, message: "不能为空", trigger: "blur" }],
         answer: [{ required: true, message: "不能为空", trigger: "blur" }],
+      },
+      // 导入
+      importMsgOpen:false,
+      importMsg: '',
+      upload: {
+        // 是否显示弹出层(文件导入)
+        open: false,
+        // 弹出层标题(文件导入)
+        title: "",
+        // 是否禁用上传
+        isUploading: false,
+        // 设置上传的请求头部
+        headers: { Authorization: "Bearer " + getToken() },
+        // 上传的地址
+        url: process.env.VUE_APP_BASE_API + "/course/courseQuestionBank/importData",
       }
     };
   },
@@ -336,17 +417,33 @@ export default {
     this.getDicts("sys_company_status").then(response => {
       this.statusOptions = response.data;
     });
-	this.getDicts("sys_course_question_type").then(response => {
-	  this.questionTypeOptions = response.data;
-	});
-
+    this.getCategoryTree()
   },
   methods: {
     getOptionLabel(index) {
       return this.alphabet[index];
     },
 
-
+    // 分类树
+    getCategoryTree() {
+      listUserCourseCategory().then(response => {
+        this.questionTypeOptions = response.data
+      });
+      getCatePidList().then(response => {
+        this.questionRootTypeOptions = response.data
+      });
+    },
+    changeCateType(val) {
+      if (!val) {
+        return
+      }
+      getCateListByPid(val).then(response => {
+        this.questionSubTypeOptions = response.data
+      })
+    },
+    getCategoryName(id) {
+      return this.questionTypeOptions.find(item => item.cateId === id)?.cateName || '';
+    },
     //题目类型发生变化
     changeType(){
         this.selectedAnswers = [];
@@ -410,6 +507,7 @@ export default {
     cancel() {
       this.open = false;
       this.reset();
+      this.changeCateType(this.queryParams.questionType)
     },
     // 表单重置
     reset() {
@@ -445,6 +543,7 @@ export default {
     /** 新增按钮操作 */
     handleAdd() {
       this.reset();
+      this.questionSubTypeOptions = []
       this.open = true;
       this.title = "添加题库";
     },
@@ -454,7 +553,7 @@ export default {
       const id = row.id || this.ids
       getCourseQuestionBank(id).then(response => {
         this.form = response.data;
-
+        this.changeCateType(this.form.questionType)
 
         //初始化多选的选择结果,单选的选择的时候,就取消其他的了 selectedAnswers
         if (this.form.type===2) {
@@ -522,7 +621,34 @@ export default {
           this.download(response.msg);
           this.exportLoading = false;
         }).catch(() => {});
-    }
+    },
+    // 下载模板
+    importTemplate() {
+      importTemplate().then((response) => {
+        this.download(response.msg);
+      });
+    },
+    // 导入
+    handleImport() {
+      this.upload.title = "导入题目";
+      this.upload.open = true;
+    },
+    submitFileForm() {
+      this.$refs.upload.submit();
+    },
+    // 文件上传中处理
+    handleFileUploadProgress(event, file, fileList) {
+      this.upload.isUploading = true;
+    },
+    // 文件上传成功处理
+    handleFileSuccess(response, file, fileList) {
+      this.upload.open = false;
+      this.upload.isUploading = false;
+      this.$refs.upload.clearFiles();
+      this.importMsgOpen=true;
+      this.importMsg = response.msg
+      this.getList();
+    },
   }
 };
 </script>

+ 32 - 11
src/views/course/courseTrafficLog/index.vue

@@ -7,7 +7,7 @@
         </el-select>
       </el-form-item>
       <el-form-item label="项目" prop="project">
-        <el-select v-model="form.project" placeholder="请选择项目" filterable clearable size="small">
+        <el-select v-model="queryParams.project" placeholder="请选择项目" filterable clearable size="small">
           <el-option
             v-for="dict in projectOptions"
             :key="dict.dictValue"
@@ -28,11 +28,12 @@
       </el-form-item>
       <el-form-item label="年月" prop="time">
         <el-date-picker
-          v-model="queryParams.time"
-          type="month"
+          v-model="time"
+          type="daterange"
           placeholder="选择年月"
           :picker-options="pickerOptions"
-          :value-format="'yyyy-MM'"
+          :value-format="'yyyy-MM-dd'"
+          @change="handleDateData"
         ></el-date-picker>
       </el-form-item>
       <el-form-item>
@@ -61,7 +62,7 @@
       <el-table-column label="公司名称" align="center" prop="companyName" />
       <el-table-column label="项目" align="center" prop="projectName" />
       <el-table-column label="课程" align="center" prop="courseName" />
-      <el-table-column label="月份" align="center" prop="month" />
+      <el-table-column label="日期" align="center" prop="month" />
       <el-table-column label="使用流量" align="center">
         <template slot-scope="scope">
           <span>{{ formatTrafficData(scope.row.totalInternetTraffic) }}</span>
@@ -93,12 +94,20 @@ export default {
   name: "CourseTrafficLog",
   data() {
     return {
-      companyList:[],
       pickerOptions: {
-        shortcuts: [{ text: '本月', onClick: () => this.handleShortcut() }],
+        shortcuts: [{
+          text: '最近一周',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+            picker.$emit('pick', [start, end]);
+          }
+        }]
       },
+      companyList:[],
       // 遮罩层
-      loading: true,
+      loading: false,
       // 导出遮罩层
       exportLoading: false,
       // 选中数组
@@ -119,8 +128,11 @@ export default {
       title: "",
       // 是否显示弹出层
       open: false,
+      time: null,
       // 查询参数
       queryParams: {
+        startDate: null,
+        endDate: null,
         pageNum: 1,
         pageSize: 10,
         userId: null,
@@ -131,7 +143,6 @@ export default {
         companyUserId: null,
         companyId: null,
         courseId: null,
-        time:new Date().toISOString().slice(0, 7)
       },
       // 表单参数
       form: {},
@@ -147,11 +158,19 @@ export default {
     this.getDicts("sys_course_project").then(response => {
       this.projectOptions = response.data;
     });
-    this.getList();
     this.getAllCompany();
 
   },
   methods: {
+    handleDateData(){
+      if (this.time) {
+        this.queryParams.startDate = this.time[0];
+        this.queryParams.endDate = this.time[1];
+      } else {
+        this.queryParams.startDate = null;
+        this.queryParams.endDate = null;
+      }
+    },
     getAllCompany() {
       allList().then(response => {
         this.companyList = response.rows;
@@ -183,8 +202,10 @@ export default {
       listCourseTrafficLog(this.queryParams).then(response => {
         this.courseTrafficLogList = response.rows;
         this.total = response.total;
+
+      }).finally(()=>{
         this.loading = false;
-      });
+      })
     },
     // 取消按钮
     cancel() {

+ 43 - 0
src/views/course/courseUserStatistics/courseUserStatisticsTabIndex.vue

@@ -0,0 +1,43 @@
+<!-- TabComponent.vue -->
+<template>
+  <div class="tab-container">
+    <el-tabs type="card" style="background-color: white">
+      <el-tab-pane label="会员">
+        <member-view/>
+      </el-tab-pane>
+      <el-tab-pane label="企微">
+        <qw-view/>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import MemberView from './index.vue';
+import QwView from './qw/index.vue';
+
+
+export default {
+  name: 'TabComponent',
+  components: {
+    MemberView,
+    QwView
+  },
+  data() {
+    return {
+    };
+  }
+};
+</script>
+
+<style scoped>
+.tab-container {
+  width: 100%;
+  height: 100%;
+}
+.app-container{
+  padding: 15px !important;
+  margin: 0px !important;
+  background-color: #fff !important;
+}
+</style>

+ 432 - 0
src/views/course/courseUserStatistics/index.vue

@@ -0,0 +1,432 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="项目" prop="courseId">
+        <el-select filterable  v-model="queryParams.project" placeholder="请选择项目"  clearable size="small">
+          <el-option
+            v-for="dict in projectLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课程" prop="courseId">
+        <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
+          <el-option
+            v-for="dict in courseLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId">
+        <el-select filterable  v-model="queryParams.videoId" placeholder="请选择小节"  clearable size="small">
+          <el-option
+            v-for="dict in videoList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="公司名称" prop="companyId">
+        <el-select style="width: 220px" filterable v-model="queryParams.companyId" placeholder="请选择公司名" clearable size="small"  @change="handleSeller">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="销售" prop="nickName" v-if="queryParams.companyId">
+        <el-select v-model="queryParams.companyUserId" remote placeholder="请选择" filterable clearable  style="width: 100%;" @keyup.enter.native="handleQuery">
+          <el-option
+            v-for="dict in companyUserList"
+            :key="`${dict.nickName} - ${dict.userName}`"
+            :label="`${dict.nickName} - ${dict.userName}`"
+            :value="dict.userId">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="添加时间" prop="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-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-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary height="600">
+      <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="销售名称" align="center" prop="companyUserName" />
+      <el-table-column label="项目" align="center" prop="projectName" />
+      <el-table-column label="课程" align="center" prop="courseName" />
+      <el-table-column label="小节" align="center" prop="videoName" />
+            <!-- 发课时间 -->
+            <el-table-column label="进线时间" align="center" prop="createTime"/>
+            <!-- 进线数 -->
+            <el-table-column label="进线数" align="center" prop="line" />
+
+            <!-- 先导课上线 -->
+            <el-table-column label="先导课上线" align="center" prop="firstOnline">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOnline }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOnline / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 先导课完课 -->
+            <el-table-column label="先导课完课" align="center" prop="firstOver">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOver }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOver / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 首日上线 -->
+            <el-table-column label="首日上线" align="center" prop="d1Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 首日完课 -->
+            <el-table-column label="首日完课" align="center" prop="d1Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 综合报名数 -->
+<!--            <el-table-column label="综合报名数" align="center" prop="sign">-->
+<!--                <template slot-scope="scope">-->
+<!--                    <span>{{ scope.row.sign }}</span>-->
+<!--                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.sign / scope.row.line) * 100).toFixed(2) }}%)</span>-->
+<!--                </template>-->
+<!--            </el-table-column>-->
+
+            <!-- 互动数 -->
+            <el-table-column label="互动数" align="center" prop="interact">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.interact }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.interact / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- A级客户 -->
+            <el-table-column label="A级客户" align="center" prop="a">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.a }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.a / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- B级客户 -->
+            <el-table-column label="B级客户" align="center" prop="b">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.b }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.b / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- C级客户 -->
+            <el-table-column label="C级客户" align="center" prop="c">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.c }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.c / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- D级客户 -->
+            <el-table-column label="D级客户" align="center" prop="d">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 流失数 -->
+<!--            <el-table-column label="流失数" align="center" prop="los">-->
+<!--                <template slot-scope="scope">-->
+<!--                    <span>{{ scope.row.los }}</span>-->
+<!--                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.los / scope.row.line) * 100).toFixed(2) }}%)</span>-->
+<!--                </template>-->
+<!--            </el-table-column>-->
+
+            <!-- 删除数 -->
+<!--            <el-table-column label="删除数" align="center" prop="del">-->
+<!--                <template slot-scope="scope">-->
+<!--                    <span>{{ scope.row.del }}</span>-->
+<!--                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.del / scope.row.line) * 100).toFixed(2) }}%)</span>-->
+<!--                </template>-->
+<!--            </el-table-column>-->
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,statisticsList,qwWatchLogStatisticsList } from "@/api/course/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import {getUserList} from "@/api/company/companyUser";
+import {getCompanyList} from "@/api/company/company";
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      companys: [],
+      activeName:"00",
+      createTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      companyUserList: [],
+      projectLists: [],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+    };
+  },
+  created() {
+    getCompanyList().then(response => {
+      this.companys = response.data;
+      if(this.companys!=null&&this.companys.length>0){
+      }
+    });
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+    this.getDicts("sys_course_project").then(response => {
+      this.projectLists = response.data;
+    })
+  },
+  methods: {
+    handleSeller(){
+      if(this.queryParams.companyId != null) {
+        getUserList(this.queryParams.companyId).then(res=>{
+          if(res.code === 200) {
+            this.companyUserList = res.data
+          }
+        })
+      }
+    },
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    change() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      qwWatchLogStatisticsList(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLog(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+  }
+};
+</script>

+ 410 - 0
src/views/course/courseUserStatistics/my.vue

@@ -0,0 +1,410 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="项目" prop="courseId">
+        <el-select filterable  v-model="queryParams.project" placeholder="请选择项目"  clearable size="small">
+          <el-option
+            v-for="dict in projectLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课程" prop="courseId">
+        <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
+          <el-option
+            v-for="dict in courseLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId">
+        <el-select filterable  v-model="queryParams.videoId" placeholder="请选择小节"  clearable size="small">
+          <el-option
+            v-for="dict in videoList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="添加时间" prop="createTime">
+        <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="change"></el-date-picker>
+      </el-form-item>
+      <el-form-item label="公司名称" prop="companyId">
+        <el-select style="width: 220px" filterable v-model="queryParams.companyId" placeholder="请选择公司名" clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </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-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary height="600">
+      <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="销售名称" align="center" prop="companyUserName" />
+        <el-table-column label="项目" align="center" prop="projectName" />
+        <el-table-column label="课程" align="center" prop="courseName" />
+        <el-table-column label="小节" align="center" prop="videoName" />
+
+            <!-- 发课时间 -->
+            <el-table-column label="进线时间" align="center" prop="createTime"/>
+            <!-- 进线数 -->
+            <el-table-column label="进线数" align="center" prop="line" />
+
+            <!-- 先导课上线 -->
+            <el-table-column label="先导课上线" align="center" prop="firstOnline">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOnline }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOnline / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 先导课完课 -->
+            <el-table-column label="先导课完课" align="center" prop="firstOver">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOver }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOver / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 首日上线 -->
+            <el-table-column label="首日上线" align="center" prop="d1Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 首日完课 -->
+            <el-table-column label="首日完课" align="center" prop="d1Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 综合报名数 -->
+            <el-table-column label="综合报名数" align="center" prop="sign">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.sign }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.sign / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 互动数 -->
+            <el-table-column label="互动数" align="center" prop="interact">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.interact }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.interact / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- A级客户 -->
+            <el-table-column label="A级客户" align="center" prop="a">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.a }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.a / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- B级客户 -->
+            <el-table-column label="B级客户" align="center" prop="b">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.b }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.b / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- C级客户 -->
+            <el-table-column label="C级客户" align="center" prop="c">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.c }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.c / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- D级客户 -->
+            <el-table-column label="D级客户" align="center" prop="d">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 流失数 -->
+            <el-table-column label="流失数" align="center" prop="los">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.los }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.los / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 删除数 -->
+            <el-table-column label="删除数" align="center" prop="del">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.del }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.del / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,statisticsList,qwWatchLogStatisticsList,myQwWatchLogStatisticsList } from "@/api/course/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import {getCompanyList} from "@/api/company/company";
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      companys:[],
+      activeName:"00",
+      createTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      projectLists: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+    };
+  },
+  created() {
+    getCompanyList().then(response => {
+      this.companys = response.data;
+
+    });
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+    this.getDicts("sys_course_project").then(response => {
+      this.projectLists = response.data;
+    })
+  },
+  methods: {
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    change() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      myQwWatchLogStatisticsList(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLog(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+  }
+};
+</script>

+ 711 - 0
src/views/course/courseUserStatistics/myStatistics.vue

@@ -0,0 +1,711 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="项目" prop="courseId">
+        <el-select filterable  v-model="queryParams.project" placeholder="请选择项目"  clearable size="small">
+          <el-option
+            v-for="dict in projectLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课程" prop="courseId">
+        <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
+          <el-option
+            v-for="dict in courseLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId">
+        <el-select filterable  v-model="queryParams.videoId" placeholder="请选择小节"  clearable size="small">
+          <el-option
+            v-for="dict in videoList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="添加时间" prop="createTime">
+        <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="change"></el-date-picker>
+      </el-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-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary height="600">
+      <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="员工名称" align="center" prop="qwUserName" />
+
+        <el-table-column label="销售名称" align="center" prop="qwUserName" />
+        <el-table-column label="项目" align="center" prop="projectName" />
+        <el-table-column label="课程" align="center" prop="courseName" />
+        <el-table-column label="小节" align="center" prop="videoName" />
+            <!-- 发课时间 -->
+            <el-table-column label="进线时间" align="center" prop="createTime"/>
+            <!-- 进线数 -->
+            <el-table-column label="进线数" align="center" prop="line" />
+
+            <!-- 先导课上线 -->
+            <el-table-column label="先导上线" align="center" prop="firstOnline">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOnline }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOnline / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 先导课完课 -->
+            <el-table-column label="先导完课" align="center" prop="firstOver">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOver }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOver / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+
+            <el-table-column label="D1上线" align="center" prop="d1Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D1完课" align="center" prop="d1Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D2上线" align="center" prop="d2Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d2Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d2Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D2完课" align="center" prop="d2Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d2Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d2Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D3上线" align="center" prop="d3Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d3Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d3Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D3完课" align="center" prop="d3Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d3Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d3Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D4上线" align="center" prop="d4Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d4Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d4Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D4完课" align="center" prop="d4Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d4Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d4Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D5上线" align="center" prop="d5Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d5Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d5Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D5完课" align="center" prop="d5Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d5Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d5Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D6上线" align="center" prop="d6Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d6Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d6Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D6完课" align="center" prop="d6Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d6Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d6Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D7上线" align="center" prop="d7Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d7Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d7Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D7完课" align="center" prop="d7Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d7Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d7Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D8上线" align="center" prop="d8Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d8Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d8Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D8完课" align="center" prop="d8Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d8Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d8Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D9上线" align="center" prop="d9Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d9Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d9Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D9完课" align="center" prop="d9Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d9Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d9Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D10上线" align="center" prop="d10Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d10Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d10Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D10完课" align="center" prop="d10Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d10Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d10Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D11上线" align="center" prop="d11Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d11Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d11Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D11完课" align="center" prop="d11Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d11Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d11Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D12上线" align="center" prop="d12Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d12Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d12Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D12完课" align="center" prop="d12Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d12Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d12Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D13上线" align="center" prop="d13Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d13Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d13Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D13完课" align="center" prop="d13Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d13Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d13Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D14上线" align="center" prop="d14Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d14Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d14Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D14完课" align="center" prop="d14Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d14Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d14Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D15上线" align="center" prop="d15Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d15Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d15Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D15完课" align="center" prop="d15Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d15Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d15Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D16上线" align="center" prop="d16Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d16Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d16Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D16完课" align="center" prop="d16Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d16Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d16Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D17上线" align="center" prop="d17Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d17Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d17Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D17完课" align="center" prop="d17Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d17Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d17Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D18上线" align="center" prop="d18Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d18Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d18Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D18完课" align="center" prop="d18Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d18Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d18Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D19上线" align="center" prop="d19Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d19Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d19Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D19完课" align="center" prop="d19Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d19Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d19Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D20上线" align="center" prop="d20Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d20Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d20Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D20完课" align="center" prop="d20Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d20Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d20Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D21上线" align="center" prop="d21Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d21Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d21Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D21完课" align="center" prop="d21Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d21Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d21Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D22上线" align="center" prop="d22Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d22Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d22Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D22完课" align="center" prop="d22Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d22Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d22Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D23上线" align="center" prop="d23Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d23Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d23Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D23完课" align="center" prop="d23Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d23Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d23Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D24上线" align="center" prop="d24Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d24Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d24Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D24完课" align="center" prop="d24Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d24Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d24Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D25上线" align="center" prop="d25Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d25Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d25Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D25完课" align="center" prop="d25Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d25Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d25Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D26上线" align="center" prop="d26Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d26Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d26Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D26完课" align="center" prop="d26Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d26Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d26Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D27上线" align="center" prop="d27Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d27Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d27Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D27完课" align="center" prop="d27Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d27Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d27Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D28上线" align="center" prop="d28Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d28Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d28Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D28完课" align="center" prop="d28Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d28Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d28Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D29上线" align="center" prop="d29Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d29Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d29Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D29完课" align="center" prop="d29Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d29Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d29Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D30上线" align="center" prop="d30Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d30Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d30Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D30完课" align="center" prop="d30Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d30Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d30Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,statisticsList,qwWatchLogStatisticsList,qwWatchLogAllStatisticsList,myQwWatchLogAllStatisticsList } from "@/api/course/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      activeName:"00",
+      createTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      projectLists: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+    };
+  },
+  created() {
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+    this.getDicts("sys_course_project").then(response => {
+      this.projectLists = response.data;
+    })
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+
+    this.getList();
+
+  },
+  methods: {
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    change() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      myQwWatchLogAllStatisticsList(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLog(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+  }
+};
+</script>

+ 43 - 0
src/views/course/courseUserStatistics/myStatisticsTabIndex.vue

@@ -0,0 +1,43 @@
+<!-- TabComponent.vue -->
+<template>
+  <div class="tab-container">
+    <el-tabs type="card" style="background-color: white">
+      <el-tab-pane label="会员">
+        <member-view/>
+      </el-tab-pane>
+      <el-tab-pane label="企微">
+        <qw-view/>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import MemberView from './myStatistics.vue';
+import QwView from './qw/myStatistics.vue';
+
+
+export default {
+  name: 'TabComponent',
+  components: {
+    MemberView,
+    QwView
+  },
+  data() {
+    return {
+    };
+  }
+};
+</script>
+
+<style scoped>
+.tab-container {
+  width: 100%;
+  height: 100%;
+}
+.app-container{
+  padding: 15px !important;
+  margin: 0px !important;
+  background-color: #fff !important;
+}
+</style>

+ 43 - 0
src/views/course/courseUserStatistics/myTabIndex.vue

@@ -0,0 +1,43 @@
+<!-- TabComponent.vue -->
+<template>
+  <div class="tab-container">
+    <el-tabs type="card" style="background-color: white">
+      <el-tab-pane label="会员">
+        <member-view/>
+      </el-tab-pane>
+      <el-tab-pane label="企微">
+        <qw-view/>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import MemberView from './my.vue';
+import QwView from './qw/my.vue';
+
+
+export default {
+  name: 'TabComponent',
+  components: {
+    MemberView,
+    QwView
+  },
+  data() {
+    return {
+    };
+  }
+};
+</script>
+
+<style scoped>
+.tab-container {
+  width: 100%;
+  height: 100%;
+}
+.app-container{
+  padding: 15px !important;
+  margin: 0px !important;
+  background-color: #fff !important;
+}
+</style>

+ 384 - 0
src/views/course/courseUserStatistics/qw/index.vue

@@ -0,0 +1,384 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="企微昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入企微昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="添加时间" prop="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-form-item>
+
+      <el-form-item label="公司名称" prop="companyId">
+        <el-select style="width: 220px" filterable v-model="queryParams.companyId" placeholder="请选择公司名" clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </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-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary>
+      <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"/>
+            <!-- 进线数 -->
+            <el-table-column label="进线数" align="center" prop="line" />
+
+            <!-- 先导课上线 -->
+            <el-table-column label="先导课上线" align="center" prop="firstOnline">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOnline }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOnline / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 先导课完课 -->
+            <el-table-column label="先导课完课" align="center" prop="firstOver">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOver }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOver / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 首日上线 -->
+            <el-table-column label="首日上线" align="center" prop="d1Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 首日完课 -->
+            <el-table-column label="首日完课" align="center" prop="d1Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 综合报名数 -->
+            <el-table-column label="综合报名数" align="center" prop="sign">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.sign }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.sign / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 互动数 -->
+            <el-table-column label="互动数" align="center" prop="interact">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.interact }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.interact / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- A级客户 -->
+            <el-table-column label="A级客户" align="center" prop="a">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.a }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.a / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- B级客户 -->
+            <el-table-column label="B级客户" align="center" prop="b">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.b }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.b / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- C级客户 -->
+            <el-table-column label="C级客户" align="center" prop="c">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.c }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.c / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- D级客户 -->
+            <el-table-column label="D级客户" align="center" prop="d">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 流失数 -->
+            <el-table-column label="流失数" align="center" prop="los">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.los }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.los / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 删除数 -->
+            <el-table-column label="删除数" align="center" prop="del">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.del }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.del / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,statisticsList,qwWatchLogStatisticsList } from "@/api/course/qw/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import {getCompanyList} from "@/api/company/company";
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      companys: [],
+      activeName:"00",
+      createTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+    };
+  },
+  created() {
+    getCompanyList().then(response => {
+      this.companys = response.data;
+      if(this.companys!=null&&this.companys.length>0){
+      }
+    });
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+  },
+  methods: {
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    change() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      qwWatchLogStatisticsList(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLog(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+  }
+};
+</script>

+ 381 - 0
src/views/course/courseUserStatistics/qw/my.vue

@@ -0,0 +1,381 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="企微昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入企微昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="添加时间" prop="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-form-item>
+      <el-form-item label="公司名称" prop="companyId">
+        <el-select style="width: 220px" filterable v-model="queryParams.companyId" placeholder="请选择公司名" clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </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-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary >
+      <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"/>
+            <!-- 进线数 -->
+            <el-table-column label="进线数" align="center" prop="line" />
+
+            <!-- 先导课上线 -->
+            <el-table-column label="先导课上线" align="center" prop="firstOnline">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOnline }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOnline / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 先导课完课 -->
+            <el-table-column label="先导课完课" align="center" prop="firstOver">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOver }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOver / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 首日上线 -->
+            <el-table-column label="首日上线" align="center" prop="d1Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 首日完课 -->
+            <el-table-column label="首日完课" align="center" prop="d1Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 综合报名数 -->
+            <el-table-column label="综合报名数" align="center" prop="sign">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.sign }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.sign / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 互动数 -->
+            <el-table-column label="互动数" align="center" prop="interact">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.interact }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.interact / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- A级客户 -->
+            <el-table-column label="A级客户" align="center" prop="a">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.a }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.a / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- B级客户 -->
+            <el-table-column label="B级客户" align="center" prop="b">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.b }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.b / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- C级客户 -->
+            <el-table-column label="C级客户" align="center" prop="c">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.c }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.c / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- D级客户 -->
+            <el-table-column label="D级客户" align="center" prop="d">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 流失数 -->
+            <el-table-column label="流失数" align="center" prop="los">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.los }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.los / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 删除数 -->
+            <el-table-column label="删除数" align="center" prop="del">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.del }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.del / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,statisticsList,qwWatchLogStatisticsList,myQwWatchLogStatisticsList } from "@/api/course/qw/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import {getCompanyList} from "@/api/company/company";
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      companys:[],
+      activeName:"00",
+      createTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+    };
+  },
+  created() {
+    getCompanyList().then(response => {
+      this.companys = response.data;
+    });
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+  },
+  methods: {
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    change() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      myQwWatchLogStatisticsList(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLog(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+  }
+};
+</script>

+ 680 - 0
src/views/course/courseUserStatistics/qw/myStatistics.vue

@@ -0,0 +1,680 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="企微昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入企微昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="添加时间" prop="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-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-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary height="600">
+      <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"/>
+            <!-- 进线数 -->
+            <el-table-column label="进线数" align="center" prop="line" />
+
+            <!-- 先导课上线 -->
+            <el-table-column label="先导上线" align="center" prop="firstOnline">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOnline }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOnline / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 先导课完课 -->
+            <el-table-column label="先导完课" align="center" prop="firstOver">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOver }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOver / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+
+            <el-table-column label="D1上线" align="center" prop="d1Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D1完课" align="center" prop="d1Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D2上线" align="center" prop="d2Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d2Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d2Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D2完课" align="center" prop="d2Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d2Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d2Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D3上线" align="center" prop="d3Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d3Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d3Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D3完课" align="center" prop="d3Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d3Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d3Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D4上线" align="center" prop="d4Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d4Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d4Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D4完课" align="center" prop="d4Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d4Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d4Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D5上线" align="center" prop="d5Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d5Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d5Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D5完课" align="center" prop="d5Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d5Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d5Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D6上线" align="center" prop="d6Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d6Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d6Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D6完课" align="center" prop="d6Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d6Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d6Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D7上线" align="center" prop="d7Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d7Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d7Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D7完课" align="center" prop="d7Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d7Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d7Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D8上线" align="center" prop="d8Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d8Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d8Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D8完课" align="center" prop="d8Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d8Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d8Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D9上线" align="center" prop="d9Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d9Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d9Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D9完课" align="center" prop="d9Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d9Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d9Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D10上线" align="center" prop="d10Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d10Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d10Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D10完课" align="center" prop="d10Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d10Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d10Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D11上线" align="center" prop="d11Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d11Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d11Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D11完课" align="center" prop="d11Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d11Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d11Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D12上线" align="center" prop="d12Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d12Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d12Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D12完课" align="center" prop="d12Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d12Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d12Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D13上线" align="center" prop="d13Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d13Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d13Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D13完课" align="center" prop="d13Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d13Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d13Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D14上线" align="center" prop="d14Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d14Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d14Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D14完课" align="center" prop="d14Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d14Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d14Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D15上线" align="center" prop="d15Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d15Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d15Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D15完课" align="center" prop="d15Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d15Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d15Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D16上线" align="center" prop="d16Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d16Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d16Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D16完课" align="center" prop="d16Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d16Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d16Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D17上线" align="center" prop="d17Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d17Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d17Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D17完课" align="center" prop="d17Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d17Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d17Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D18上线" align="center" prop="d18Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d18Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d18Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D18完课" align="center" prop="d18Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d18Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d18Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D19上线" align="center" prop="d19Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d19Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d19Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D19完课" align="center" prop="d19Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d19Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d19Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D20上线" align="center" prop="d20Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d20Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d20Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D20完课" align="center" prop="d20Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d20Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d20Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D21上线" align="center" prop="d21Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d21Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d21Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D21完课" align="center" prop="d21Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d21Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d21Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D22上线" align="center" prop="d22Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d22Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d22Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D22完课" align="center" prop="d22Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d22Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d22Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D23上线" align="center" prop="d23Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d23Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d23Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D23完课" align="center" prop="d23Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d23Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d23Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D24上线" align="center" prop="d24Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d24Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d24Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D24完课" align="center" prop="d24Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d24Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d24Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D25上线" align="center" prop="d25Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d25Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d25Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D25完课" align="center" prop="d25Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d25Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d25Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D26上线" align="center" prop="d26Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d26Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d26Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D26完课" align="center" prop="d26Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d26Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d26Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D27上线" align="center" prop="d27Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d27Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d27Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D27完课" align="center" prop="d27Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d27Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d27Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D28上线" align="center" prop="d28Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d28Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d28Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D28完课" align="center" prop="d28Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d28Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d28Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D29上线" align="center" prop="d29Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d29Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d29Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D29完课" align="center" prop="d29Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d29Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d29Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D30上线" align="center" prop="d30Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d30Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d30Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D30完课" align="center" prop="d30Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d30Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d30Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,statisticsList,qwWatchLogStatisticsList,qwWatchLogAllStatisticsList,myQwWatchLogAllStatisticsList } from "@/api/course/qw/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      activeName:"00",
+      createTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+    };
+  },
+  created() {
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+  },
+  methods: {
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    change() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      myQwWatchLogAllStatisticsList(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLog(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+  }
+};
+</script>

+ 697 - 0
src/views/course/courseUserStatistics/qw/statistics.vue

@@ -0,0 +1,697 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="企微昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入企微昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="添加时间" prop="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-form-item>
+      <el-form-item label="公司名称" prop="companyId">
+        <el-select style="width: 220px" filterable v-model="queryParams.companyId" placeholder="请选择公司名" clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </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-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary height="600">
+      <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"/>
+            <!-- 进线数 -->
+            <el-table-column label="进线数" align="center" prop="line" />
+
+            <!-- 先导课上线 -->
+            <el-table-column label="先导上线" align="center" prop="firstOnline">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOnline }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOnline / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 先导课完课 -->
+            <el-table-column label="先导完课" align="center" prop="firstOver">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOver }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOver / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+
+            <el-table-column label="D1上线" align="center" prop="d1Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D1完课" align="center" prop="d1Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D2上线" align="center" prop="d2Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d2Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d2Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D2完课" align="center" prop="d2Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d2Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d2Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D3上线" align="center" prop="d3Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d3Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d3Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D3完课" align="center" prop="d3Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d3Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d3Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D4上线" align="center" prop="d4Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d4Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d4Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D4完课" align="center" prop="d4Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d4Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d4Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D5上线" align="center" prop="d5Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d5Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d5Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D5完课" align="center" prop="d5Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d5Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d5Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D6上线" align="center" prop="d6Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d6Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d6Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D6完课" align="center" prop="d6Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d6Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d6Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D7上线" align="center" prop="d7Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d7Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d7Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D7完课" align="center" prop="d7Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d7Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d7Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D8上线" align="center" prop="d8Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d8Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d8Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D8完课" align="center" prop="d8Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d8Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d8Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D9上线" align="center" prop="d9Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d9Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d9Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D9完课" align="center" prop="d9Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d9Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d9Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D10上线" align="center" prop="d10Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d10Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d10Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D10完课" align="center" prop="d10Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d10Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d10Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D11上线" align="center" prop="d11Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d11Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d11Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D11完课" align="center" prop="d11Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d11Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d11Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D12上线" align="center" prop="d12Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d12Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d12Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D12完课" align="center" prop="d12Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d12Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d12Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D13上线" align="center" prop="d13Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d13Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d13Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D13完课" align="center" prop="d13Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d13Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d13Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D14上线" align="center" prop="d14Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d14Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d14Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D14完课" align="center" prop="d14Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d14Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d14Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D15上线" align="center" prop="d15Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d15Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d15Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D15完课" align="center" prop="d15Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d15Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d15Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D16上线" align="center" prop="d16Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d16Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d16Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D16完课" align="center" prop="d16Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d16Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d16Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D17上线" align="center" prop="d17Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d17Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d17Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D17完课" align="center" prop="d17Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d17Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d17Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D18上线" align="center" prop="d18Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d18Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d18Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D18完课" align="center" prop="d18Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d18Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d18Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D19上线" align="center" prop="d19Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d19Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d19Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D19完课" align="center" prop="d19Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d19Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d19Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D20上线" align="center" prop="d20Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d20Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d20Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D20完课" align="center" prop="d20Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d20Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d20Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D21上线" align="center" prop="d21Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d21Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d21Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D21完课" align="center" prop="d21Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d21Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d21Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D22上线" align="center" prop="d22Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d22Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d22Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D22完课" align="center" prop="d22Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d22Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d22Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D23上线" align="center" prop="d23Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d23Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d23Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D23完课" align="center" prop="d23Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d23Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d23Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D24上线" align="center" prop="d24Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d24Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d24Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D24完课" align="center" prop="d24Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d24Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d24Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D25上线" align="center" prop="d25Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d25Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d25Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D25完课" align="center" prop="d25Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d25Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d25Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D26上线" align="center" prop="d26Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d26Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d26Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D26完课" align="center" prop="d26Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d26Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d26Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D27上线" align="center" prop="d27Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d27Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d27Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D27完课" align="center" prop="d27Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d27Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d27Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D28上线" align="center" prop="d28Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d28Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d28Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D28完课" align="center" prop="d28Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d28Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d28Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D29上线" align="center" prop="d29Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d29Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d29Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D29完课" align="center" prop="d29Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d29Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d29Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D30上线" align="center" prop="d30Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d30Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d30Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D30完课" align="center" prop="d30Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d30Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d30Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,statisticsList,qwWatchLogStatisticsList,qwWatchLogAllStatisticsList } from "@/api/course/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import {getCompanyList} from "@/api/company/company";
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      companys: [],
+      activeName:"00",
+      createTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+    };
+  },
+  created() {
+    getCompanyList().then(response => {
+      this.companys = response.data;
+      if(this.companys!=null&&this.companys.length>0){
+      }
+    });
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+  },
+  methods: {
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    change() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      qwWatchLogAllStatisticsList(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+      }).finally(()=>{
+        this.loading = false;
+      })
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLog(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+  }
+};
+</script>

+ 752 - 0
src/views/course/courseUserStatistics/statistics.vue

@@ -0,0 +1,752 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="项目" prop="courseId">
+        <el-select filterable  v-model="queryParams.project" placeholder="请选择项目"  clearable size="small">
+          <el-option
+            v-for="dict in projectLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="课程" prop="courseId">
+        <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
+          <el-option
+            v-for="dict in courseLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId">
+        <el-select filterable  v-model="queryParams.videoId" placeholder="请选择小节"  clearable size="small">
+          <el-option
+            v-for="dict in videoList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="公司名称" prop="companyId">
+        <el-select style="width: 220px" @change="handleSeller" filterable v-model="queryParams.companyId" placeholder="请选择公司名" clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="销售" prop="nickName" v-if="queryParams.companyId">
+        <el-select v-model="queryParams.companyUserId" remote placeholder="请选择" filterable clearable @keyup.enter.native="handleQuery">
+          <el-option
+            v-for="dict in companyUserList"
+            :key="`${dict.nickName} - ${dict.userName}`"
+            :label="`${dict.nickName} - ${dict.userName}`"
+            :value="dict.userId">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="添加时间" prop="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-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-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary height="600">
+      <el-table-column type="selection" width="55" align="center" />
+<!--        <el-table-column label="会员名称" align="center" prop="fsUserName" />-->
+      <el-table-column label="销售名称" align="center" prop="qwUserName" />
+      <el-table-column label="项目" align="center" prop="projectName" />
+      <el-table-column label="课程" align="center" prop="courseName" />
+      <el-table-column label="小节" align="center" prop="videoName" />
+            <!-- 发课时间 -->
+            <el-table-column label="进线时间" align="center" prop="createTime"/>
+            <!-- 进线数 -->
+            <el-table-column label="进线数" align="center" prop="line" />
+
+            <!-- 先导课上线 -->
+            <el-table-column label="先导上线" align="center" prop="firstOnline">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOnline }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOnline / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <!-- 先导课完课 -->
+            <el-table-column label="先导完课" align="center" prop="firstOver">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.firstOver }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.firstOver / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+
+            <el-table-column label="D1上线" align="center" prop="d1Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D1完课" align="center" prop="d1Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d1Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d1Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D2上线" align="center" prop="d2Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d2Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d2Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D2完课" align="center" prop="d2Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d2Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d2Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D3上线" align="center" prop="d3Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d3Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d3Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D3完课" align="center" prop="d3Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d3Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d3Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D4上线" align="center" prop="d4Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d4Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d4Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D4完课" align="center" prop="d4Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d4Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d4Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D5上线" align="center" prop="d5Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d5Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d5Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D5完课" align="center" prop="d5Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d5Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d5Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D6上线" align="center" prop="d6Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d6Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d6Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D6完课" align="center" prop="d6Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d6Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d6Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D7上线" align="center" prop="d7Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d7Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d7Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D7完课" align="center" prop="d7Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d7Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d7Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D8上线" align="center" prop="d8Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d8Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d8Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D8完课" align="center" prop="d8Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d8Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d8Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D9上线" align="center" prop="d9Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d9Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d9Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D9完课" align="center" prop="d9Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d9Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d9Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D10上线" align="center" prop="d10Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d10Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d10Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D10完课" align="center" prop="d10Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d10Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d10Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D11上线" align="center" prop="d11Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d11Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d11Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D11完课" align="center" prop="d11Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d11Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d11Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D12上线" align="center" prop="d12Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d12Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d12Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D12完课" align="center" prop="d12Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d12Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d12Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D13上线" align="center" prop="d13Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d13Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d13Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D13完课" align="center" prop="d13Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d13Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d13Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D14上线" align="center" prop="d14Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d14Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d14Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D14完课" align="center" prop="d14Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d14Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d14Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D15上线" align="center" prop="d15Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d15Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d15Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D15完课" align="center" prop="d15Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d15Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d15Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D16上线" align="center" prop="d16Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d16Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d16Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D16完课" align="center" prop="d16Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d16Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d16Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D17上线" align="center" prop="d17Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d17Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d17Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D17完课" align="center" prop="d17Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d17Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d17Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D18上线" align="center" prop="d18Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d18Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d18Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D18完课" align="center" prop="d18Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d18Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d18Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D19上线" align="center" prop="d19Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d19Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d19Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D19完课" align="center" prop="d19Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d19Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d19Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D20上线" align="center" prop="d20Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d20Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d20Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D20完课" align="center" prop="d20Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d20Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d20Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D21上线" align="center" prop="d21Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d21Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d21Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D21完课" align="center" prop="d21Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d21Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d21Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D22上线" align="center" prop="d22Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d22Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d22Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D22完课" align="center" prop="d22Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d22Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d22Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D23上线" align="center" prop="d23Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d23Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d23Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D23完课" align="center" prop="d23Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d23Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d23Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D24上线" align="center" prop="d24Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d24Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d24Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D24完课" align="center" prop="d24Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d24Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d24Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D25上线" align="center" prop="d25Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d25Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d25Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D25完课" align="center" prop="d25Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d25Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d25Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D26上线" align="center" prop="d26Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d26Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d26Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D26完课" align="center" prop="d26Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d26Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d26Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D27上线" align="center" prop="d27Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d27Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d27Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D27完课" align="center" prop="d27Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d27Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d27Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D28上线" align="center" prop="d28Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d28Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d28Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D28完课" align="center" prop="d28Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d28Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d28Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D29上线" align="center" prop="d29Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d29Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d29Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D29完课" align="center" prop="d29Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d29Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d29Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="D30上线" align="center" prop="d30Online">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d30Online }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d30Online / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="D30完课" align="center" prop="d30Over">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.d30Over }}</span>
+                    <span style="font-size: 12px; color: #959595;margin-left: 5px;">({{ ((scope.row.d30Over / scope.row.line) * 100).toFixed(2) }}%)</span>
+                </template>
+            </el-table-column>
+
+
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,statisticsList,qwWatchLogStatisticsList,qwWatchLogAllStatisticsList } from "@/api/course/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import {getUserList} from "@/api/company/companyUser";
+import {getCompanyList} from "@/api/company/company";
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      companys:[],
+      activeName:"00",
+      createTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: false,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      companyUserList: [],
+      projectLists: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+    };
+  },
+  created() {
+    getCompanyList().then(response => {
+      this.companys = response.data;
+      if(this.companys!=null&&this.companys.length>0){
+      }
+    });
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    // this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+    this.getDicts("sys_course_project").then(response => {
+      this.projectLists = response.data;
+    })
+  },
+  methods: {
+    handleSeller(){
+      if(this.queryParams.companyId != null) {
+        getUserList(this.queryParams.companyId).then(res=>{
+          if(res.code === 200) {
+            this.companyUserList = res.data
+          }
+        })
+      }
+    },
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    change() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      qwWatchLogAllStatisticsList(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+      }).finally(()=>{
+        this.loading = false;
+      })
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      if(this.queryParams.companyId == null) {
+        this.$message.warning("公司为必选!");
+        return;
+      }
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLog(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+  }
+};
+</script>

+ 43 - 0
src/views/course/courseUserStatistics/statisticsTabIndex.vue

@@ -0,0 +1,43 @@
+<!-- TabComponent.vue -->
+<template>
+  <div class="tab-container">
+    <el-tabs type="card" style="background-color: white">
+      <el-tab-pane label="会员">
+        <member-view/>
+      </el-tab-pane>
+      <el-tab-pane label="企微">
+        <qw-view/>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import MemberView from './statistics.vue';
+import QwView from './qw/statistics.vue';
+
+
+export default {
+  name: 'TabComponent',
+  components: {
+    MemberView,
+    QwView
+  },
+  data() {
+    return {
+    };
+  }
+};
+</script>
+
+<style scoped>
+.tab-container {
+  width: 100%;
+  height: 100%;
+}
+.app-container{
+  padding: 15px !important;
+  margin: 0px !important;
+  background-color: #fff !important;
+}
+</style>

+ 178 - 34
src/views/course/courseWatchLog/index.vue

@@ -1,28 +1,30 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="会员ID" prop="userId">
-        <el-input
-          v-model="queryParams.userId"
-          placeholder="请输入会员ID"
-          clearable
-          size="small"
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="公司名称" prop="companyId" >
-        <el-select v-model="queryParams.companyId" placeholder="请选择所属公司" filterable clearable size="small">
-          <el-option v-for="(option, index) in companyList" :key="index" :value="option.dictValue" :label="option.dictLabel"></el-option>
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="用户" prop="userId">
+        <el-select v-model="queryParams.userId" remote placeholder="用户名/手机号" filterable clearable  style="width: 100%;"
+                   @keyup.enter.native="handleQuery"
+                   :remote-method="remoteGetFsUserList"
+                   @clear="handleClear"
+                   :loading="queryUserLoading"
+        >
+          <el-option
+            v-for="dict in fsUserList"
+            :key="`${dict.nickname} - ${dict.phone}`"
+            :label="`${dict.nickname} - ${dict.phone}`"
+            :value="dict.userId">
+          </el-option>
         </el-select>
       </el-form-item>
       <el-form-item label="所属销售" prop="companyUserName">
-        <el-input
-          v-model="queryParams.companyUserName"
-          placeholder="请输入所属销售"
-          clearable
-          size="small"
-          @keyup.enter.native="handleQuery"
-        />
+        <el-select v-model="queryParams.companyUserId" remote placeholder="请选择" filterable clearable  style="width: 100%;" @keyup.enter.native="handleQuery">
+          <el-option
+            v-for="dict in companyUserList"
+            :key="`${dict.nickName} - ${dict.userName}`"
+            :label="`${dict.nickName} - ${dict.userName}`"
+            :value="dict.userId">
+          </el-option>
+        </el-select>
       </el-form-item>
       <el-form-item label="课程" prop="courseId">
         <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
@@ -44,8 +46,37 @@
           />
         </el-select>
       </el-form-item>
+      <el-form-item label="营期时间" prop="scheduleTime">
+        <el-date-picker
+          v-model="scheduleTime"
+          type="daterange"
+          size="small"
+          style="width: 240px"
+          value-format="yyyy-MM-dd"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          @change="handleScheduleTimeChange">
+        </el-date-picker>
+      </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="结束日期" @change="createChange"></el-date-picker>
+      </el-form-item>
+      <el-form-item label="最新更新时间" prop="updateTime">
+        <el-date-picker v-model="updateTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange"
+                        range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="updateChange"></el-date-picker>
+      </el-form-item>
+
+      <el-form-item label="类型" prop="type">
+        <el-select filterable  v-model="sourceTypeModel" placeholder="请选择小节"  clearable size="small">
+          <el-option
+            v-for="dict in userSourceTypeOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
       </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@@ -75,9 +106,28 @@
     <el-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="记录编号" align="center" prop="logId" />
-      <el-table-column label="会员ID" align="center" prop="userId" />
+      <el-table-column label="用户账号" align="center" prop="userName" />
+      <el-table-column label="企微客户" align="center" prop="externalUserName" v-if="queryParams.sourceType == 2"/>
+      <el-table-column label="会员昵称" align="center" prop="fsNickName">
+        <template slot-scope="scope">
+          <div style="display: flex;white-space: nowrap">
+            <div style="margin: auto">
+              {{scope.row.fsNickName}}
+            </div>
+            <el-popover
+              placement="right"
+              title=""
+              trigger="hover">
+              <img slot="reference" :src="scope.row.fsAvatar" style="width: 30px;height: 30px">
+              <img :src="scope.row.fsAvatar" style="max-width: 200px;max-height: 200px">
+            </el-popover>
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column label="项目" align="center" prop="projectName" />
       <el-table-column label="课程名称" align="center" prop="courseName" />
       <el-table-column label="小节名称" align="center" prop="videoName" />
+      <el-table-column label="企微员工名称" align="center" prop="qwUserName" v-if="queryParams.sourceType == 2"/>
       <el-table-column label="记录类型" align="center" prop="logType">
         <template slot-scope="scope">
           <dict-tag :options="logTypeOptions" :value="scope.row.logType"/>
@@ -85,9 +135,13 @@
       </el-table-column>
       <el-table-column label="播放时长" align="center" prop="duration" />
       <el-table-column label="所属销售" align="center" prop="companyUserName" />
-      <el-table-column label="所属公司" align="center" prop="companyName" />
+<!--      <el-table-column label="所属公司" align="center" prop="companyName" />-->
+<!--      <el-table-column label="企微员工名称" align="center" prop="qwUserName" />-->
       <el-table-column label="所属发送方式" align="center" prop="sendType" />
       <el-table-column label="创建时间" align="center" prop="createTime" />
+      <el-table-column label="更新时间" align="center" prop="updateTime" />
+      <el-table-column label="完课时间" align="center" prop="finishTime" />
+      <el-table-column label="营期时间" align="center" prop="campPeriodTime" />
     </el-table>
 
     <pagination
@@ -105,16 +159,20 @@
 import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog } from "@/api/course/courseWatchLog";
 import {allList}from "@/api/company/company";
 import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import {getUserList} from "@/api/company/companyUser";
+import {getFsUserList} from "@/api/users/user";
 export default {
   name: "CourseWatchLog",
   data() {
     return {
+      userSourceTypeOptions: [],
       activeName:"00",
       createTime:null,
+      updateTime:null,
       courseLists:[],
       videoList:[],
-      companyList:[],
       logTypeOptions:[],
+      queryUserLoading: false,
       // 遮罩层
       loading: true,
       // 导出遮罩层
@@ -129,20 +187,25 @@ export default {
       showSearch: true,
       // 总条数
       total: 0,
+      companyUserList: [],
       // 短链课程看课记录表格数据
       courseWatchLogList: [],
+      fsUserList: [],
       // 弹出层标题
       title: "",
       // 是否显示弹出层
       open: false,
+
       // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
         userId: null,
+        nickName: null,
         videoId: null,
         logType: null,
         qwExternalContactId: null,
+        externalUserName:null,
         duration: null,
         qwUserId: null,
         companyUserId: null,
@@ -150,25 +213,78 @@ export default {
         courseId: null,
         sTime:null,
         eTime:null,
+        upSTime:null,
+        upETime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+        sourceType: 1
       },
       // 表单参数
       form: {},
       // 表单校验
       rules: {
-      }
+      },
+      scheduleTime: null,
     };
   },
   created() {
     courseList().then(response => {
       this.courseLists = response.list;
     });
-    this.getAllCompany();
     this.getList();
     this.getDicts("sys_course_watch_log_type").then(response => {
       this.logTypeOptions = response.data;
     });
+    getUserList().then(res=>{
+      if(res.code === 200) {
+        this.companyUserList = res.data
+      }
+    })
+
+    this.getDicts('user_source_type').then(response => {
+      this.userSourceTypeOptions = response.data;
+    })
+
+  },
+  computed: {
+    sourceTypeModel: {
+      get() {
+        return this.queryParams.sourceType !== null && this.queryParams.sourceType !== undefined ? this.queryParams.sourceType.toString() : null;
+      },
+      set(newVal) {
+        this.queryParams.sourceType = newVal;
+      }
+    }
   },
   methods: {
+    handleClear(){
+      this.queryUserLoading = false;
+      this.fsUserList = [];
+    },
+    remoteGetFsUserList(query){
+      if(query){
+        this.queryUserLoading = true;
+        const isNumeric = /^\d+$/.test(query);
+        let payload = {
+          username: query,
+          nickname: query
+        }
+        if(isNumeric) {
+          payload = {
+            userId: query,
+            username: query,
+            phone: query
+          }
+        }
+        getFsUserList(payload).then(res=>{
+          if(res.code === 200) {
+            this.fsUserList = res.data
+          }
+        }).finally(()=>{
+          this.queryUserLoading = false;
+        })
+      }
+    },
     courseChange(row){
       this.queryParams.videoId=null;
       if(row === ''){
@@ -179,7 +295,7 @@ export default {
         this.videoList=response.list
       });
     },
-    change() {
+    createChange() {
       if (this.createTime != null) {
         this.queryParams.sTime = this.createTime[0];
         this.queryParams.eTime = this.createTime[1];
@@ -188,11 +304,15 @@ export default {
         this.queryParams.eTime = null;
       }
     },
-    getAllCompany() {
-      allList().then(response => {
-        this.companyList = response.rows;
-        this.companyList.push({dictValue:"-1",dictLabel:"无"})
-      });
+
+    updateChange(){
+      if (this.updateTime != null) {
+        this.queryParams.upSTime = this.updateTime[0];
+        this.queryParams.upETime = this.updateTime[1];
+      } else {
+        this.queryParams.upSTime = null;
+        this.queryParams.upETime = null;
+      }
     },
     handleClickX(tab,event){
       this.activeName=tab.name;
@@ -206,6 +326,10 @@ export default {
     /** 查询短链课程看课记录列表 */
     getList() {
       this.loading = true;
+      if(this.queryParams.logType == "10"){
+        this.queryParams.logType = null;
+      }
+
       listCourseWatchLog(this.queryParams).then(response => {
         this.courseWatchLogList = response.rows;
         this.total = response.total;
@@ -227,12 +351,16 @@ export default {
         createTime: null,
         updateTime: null,
         qwExternalContactId: null,
+        externalUserName:null,
         duration: null,
         qwUserId: null,
         companyUserId: null,
         companyId: null,
-        courseId: null
+        courseId: null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
       };
+      this.scheduleTime=null;
       this.resetForm("form");
     },
     /** 搜索按钮操作 */
@@ -244,8 +372,15 @@ export default {
     resetQuery() {
       this.resetForm("queryForm");
       this.createTime = null;
+      this.scheduleTime = null;
       this.queryParams.sTime = null;
       this.queryParams.eTime = null;
+      this.queryParams.upSTime = null;
+      this.queryParams.upETime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.scheduleTime=null;
+      this.updateTime=null;
       this.handleQuery();
     },
     // 多选框选中数据
@@ -318,7 +453,16 @@ export default {
           this.download(response.msg);
           this.exportLoading = false;
         }).catch(() => {});
-    }
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
   }
 };
 </script>

+ 388 - 0
src/views/course/courseWatchLog/myCourseWatchLog.vue

@@ -0,0 +1,388 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="企微账号" prop="qwUserId">
+        <el-select v-model="queryParams.qwUserId" placeholder="企微账号" clearable size="small" @change="updateQwuser()">
+          <el-option
+            v-for="dict in myQwUserList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel + '('+dict.corpName+')'"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="客户ID" prop="qwExternalContactId">
+        <el-input
+          v-model="queryParams.qwExternalContactId"
+          placeholder="请输入会员ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="会员ID" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入会员ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="会员昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入会员昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="企微客户昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.externalUserName"
+          placeholder="请输入企微客户昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="课程" prop="courseId">
+        <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
+          <el-option
+            v-for="dict in courseLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId">
+        <el-select filterable  v-model="queryParams.videoId" placeholder="请选择小节"  clearable size="small">
+          <el-option
+            v-for="dict in videoList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="change"></el-date-picker>
+      </el-form-item>
+      <el-table-column label="完课时间" align="center" prop="finishTime" />
+      <el-table-column label="营期时间" align="center" prop="campPeriodTime" />
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['course:courseWatchLog:myExport']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-tabs type="card" v-model="queryParams.logType" @tab-click="handleClickX">
+      <el-tab-pane label="全部" name="10"></el-tab-pane>
+      <el-tab-pane v-for="(item,index) in logTypeOptions" :label="item.dictLabel" :name="item.dictValue"></el-tab-pane>
+    </el-tabs>
+    <el-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="记录编号" align="center" prop="logId" />
+      <el-table-column label="企微客户" align="center" prop="externalUserName"/>
+      <el-table-column label="会员ID" align="center" prop="userId" />
+      <el-table-column label="会员昵称" align="center" prop="fsNickName">
+        <template slot-scope="scope">
+          <div style="display: flex;white-space: nowrap">
+            <div style="margin: auto">
+              {{scope.row.fsNickName}}
+            </div>
+            <el-popover
+              placement="right"
+              title=""
+              trigger="hover">
+              <img slot="reference" :src="scope.row.fsAvatar" style="width: 30px;height: 30px">
+              <img :src="scope.row.fsAvatar" style="max-width: 200px;max-height: 200px">
+            </el-popover>
+          </div>
+        </template>
+      </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="logType">
+        <template slot-scope="scope">
+          <dict-tag :options="logTypeOptions" :value="scope.row.logType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="播放时长" align="center" prop="duration" />
+      <el-table-column label="所属销售" align="center" prop="companyUserName" />
+<!--      <el-table-column label="所属公司" align="center" prop="companyName" />-->
+      <el-table-column label="企微员工名称" align="center" prop="qwUserName" />
+      <el-table-column label="企微账号" align="center" prop="qwUserName" />
+<!--      <el-table-column label="所属发送方式" align="center" prop="sendType" />-->
+      <el-table-column label="创建时间" align="center" prop="createTime" />
+<!--      <el-table-column label="更新时间" align="center" prop="updateTime" />-->
+      <el-table-column label="完课时间" align="center" prop="lastHeartbeatTime" />
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { myListCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog } from "@/api/course/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import {getMyQwUserList} from "@/api/qw/user";
+import {allListTagGroup} from "@/api/qw/tagGroup";
+import {listTag} from "@/api/qw/tag";
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      createTime:null,
+      courseLists:[],
+      videoList:[],
+      myQwUserList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        videoId: null,
+        nickName:null,
+        logType: "10",
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+
+    getMyQwUserList().then(response => {
+      this.myQwUserList = response.data;
+    });
+  },
+  methods: {
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    updateQwuser(){
+      for (const user of this.myQwUserList) {
+        if (user.dictValue == this.queryParams.qwUserId) {
+          this.queryParams.corpId=user.corpId;
+          break;
+        }
+      }
+      this.getList();
+    },
+    change() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+
+    handleClickX(tab){
+      if(tab.name==="10"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      myListCourseWatchLog(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      };
+      this.scheduleTime=null;
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.scheduleTime=null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLog(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 43 - 0
src/views/course/courseWatchLog/myWatchLogTabIndex.vue

@@ -0,0 +1,43 @@
+<!-- TabComponent.vue -->
+<template>
+  <div class="tab-container">
+    <el-tabs type="card" style="background-color: white">
+      <el-tab-pane label="会员">
+        <member-view/>
+      </el-tab-pane>
+      <el-tab-pane label="企微">
+        <qw-view/>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import MemberView from './watchLog.vue';
+import QwView from './qw/watchLog.vue';
+
+
+export default {
+  name: 'TabComponent',
+  components: {
+    MemberView,
+    QwView
+  },
+  data() {
+    return {
+    };
+  }
+};
+</script>
+
+<style scoped>
+.tab-container {
+  width: 100%;
+  height: 100%;
+}
+.app-container{
+  padding: 15px !important;
+  margin: 0px !important;
+  background-color: #fff !important;
+}
+</style>

+ 489 - 0
src/views/course/courseWatchLog/qw/index.vue

@@ -0,0 +1,489 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="会员ID" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入会员ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="会员昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入会员昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="企微客户昵称" prop="nickName" >
+        <el-input
+          v-model="queryParams.externalUserName"
+          placeholder="请输入企微客户昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="所属销售" prop="companyUserId">
+        <el-select v-model="queryParams.companyUserId" clearable filterable remote
+                   placeholder="请输入关键词" :remote-method="loadCompanyUserOptions"
+                   v-select-load-more="loadMoreCompanyUserOptions"
+                   :loading="companyUserOptionsLoading">
+          <el-option
+            v-for="item in companyUserOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课程" prop="courseId">
+        <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
+          <el-option
+            v-for="dict in courseLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId">
+        <el-select filterable  v-model="queryParams.videoId" placeholder="请选择小节"  clearable size="small">
+          <el-option
+            v-for="dict in videoList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="企微ID" prop="qwUserUserId">
+        <el-input
+          v-model="queryParams.qwUserUserId"
+          placeholder="请输入所属企微ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="企微员工名称" prop="qwUserName">
+        <el-input
+          v-model="queryParams.qwUserName"
+          placeholder="请输入所属企微员工名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="部门名称" prop="deptName">
+        <el-input
+          v-model="queryParams.deptName"
+          placeholder="请输入部门名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="营期时间" prop="scheduleTime">
+        <el-date-picker
+          v-model="scheduleTime"
+          type="daterange"
+          size="small"
+          style="width: 240px"
+          value-format="yyyy-MM-dd"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          @change="handleScheduleTimeChange">
+        </el-date-picker>
+      </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="createChange"></el-date-picker>
+      </el-form-item>
+      <el-form-item label="最新更新时间" prop="updateTime">
+        <el-date-picker v-model="updateTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange"
+                        range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="updateChange"></el-date-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['course:courseWatchLog:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-tabs type="card" v-model="activeName" @tab-click="handleClickX">
+      <el-tab-pane label="全部" name="00"></el-tab-pane>
+      <el-tab-pane v-for="(item,index) in logTypeOptions" :label="item.dictLabel" :name="item.dictValue"></el-tab-pane>
+    </el-tabs>
+    <el-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="记录编号" align="center" prop="logId" />
+      <el-table-column label="企微客户" align="center" prop="externalUserName"/>
+      <el-table-column label="会员ID" align="center" prop="userId" />
+      <el-table-column label="会员昵称" align="center" prop="fsNickName">
+        <template slot-scope="scope">
+          <div style="display: flex;white-space: nowrap">
+            <div style="margin: auto">
+              {{scope.row.fsNickName}}
+            </div>
+            <el-popover
+              placement="right"
+              title=""
+              trigger="hover">
+              <img slot="reference" :src="scope.row.fsAvatar" style="width: 30px;height: 30px">
+              <img :src="scope.row.fsAvatar" style="max-width: 200px;max-height: 200px">
+            </el-popover>
+          </div>
+        </template>
+      </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="logType">
+        <template slot-scope="scope">
+          <dict-tag :options="logTypeOptions" :value="scope.row.logType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="播放时长" align="center" prop="duration" />
+      <el-table-column label="所属销售" align="center" prop="companyUserName" />
+<!--      <el-table-column label="所属公司" align="center" prop="companyName" />-->
+      <el-table-column label="企微员工名称" align="center" prop="qwUserName" />
+<!--      <el-table-column label="所属发送方式" align="center" prop="sendType" />-->
+      <el-table-column label="创建时间" align="center" prop="createTime" />
+      <el-table-column label="更新时间" align="center" prop="updateTime" />
+      <el-table-column label="完课时间" align="center" prop="finishTime" />
+      <el-table-column label="营期时间" align="center" prop="campPeriodTime" />
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog } from "@/api/course/qw/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import { getCompanyUserListLikeName } from "@/api/company/companyUser";
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      activeName:"00",
+      createTime:null,
+      updateTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        qwUserName: null, //企微名称
+        qwUserUserId: null, //企微id
+        deptName: null, //部门名称
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        upSTime:null,
+        upETime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+      // 员工选项列表
+      companyUserOptionsParams: {
+        name: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      companyUserOptionsLoading: false,
+      companyUserOptions: [],
+    };
+  },
+  created() {
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+  },
+  methods: {
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    createChange() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+
+    updateChange(){
+      if (this.updateTime != null) {
+        this.queryParams.upSTime = this.updateTime[0];
+        this.queryParams.upETime = this.updateTime[1];
+      } else {
+        this.queryParams.upSTime = null;
+        this.queryParams.upETime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      if(this.queryParams.logType == "10"){
+        this.queryParams.logType = null;
+      }
+
+      listCourseWatchLog(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      };
+      this.scheduleTime=null;
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.upSTime = null;
+      this.queryParams.upETime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.scheduleTime=null;
+      this.updateTime=null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLog(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+    /**
+     * 根据名称模糊查询用户列表
+     * @param query 参数
+     */
+    loadCompanyUserOptions(query) {
+      this.companyUserOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.companyUserOptionsParams.pageNum = 1
+      this.companyUserOptionsParams.name = query
+      this.companyUserOptionsLoading = true;
+      this.getCompanyUserListLikeName()
+    },
+    /**
+     * 获取员工列表
+     */
+    getCompanyUserListLikeName() {
+      getCompanyUserListLikeName(this.companyUserOptionsParams).then(response => {
+        this.companyUserOptions = [...this.companyUserOptions, ...response.data.list]
+        this.companyUserOptionsParams.hasNextPage = response.data.hasNextPage
+        this.companyUserOptionsLoading = false;
+      });
+    },
+    /**
+     * 加载更多员工选项
+     */
+    loadMoreCompanyUserOptions() {
+      if (!this.companyUserOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.companyUserOptionsParams.pageNum += 1
+      this.getCompanyUserListLikeName()
+    },
+  }
+};
+</script>

+ 396 - 0
src/views/course/courseWatchLog/qw/myCourseWatchLog.vue

@@ -0,0 +1,396 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="企微账号" prop="qwUserId">
+        <el-select v-model="queryParams.qwUserId" placeholder="企微账号" clearable size="small" @change="updateQwuser()">
+          <el-option
+            v-for="dict in myQwUserList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel + '('+dict.corpName+')'"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="客户ID" prop="qwExternalContactId">
+        <el-input
+          v-model="queryParams.qwExternalContactId"
+          placeholder="请输入会员ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="会员ID" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入会员ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="会员昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入会员昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="企微客户昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.externalUserName"
+          placeholder="请输入企微客户昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="课程" prop="courseId">
+        <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
+          <el-option
+            v-for="dict in courseLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId">
+        <el-select filterable  v-model="queryParams.videoId" placeholder="请选择小节"  clearable size="small">
+          <el-option
+            v-for="dict in videoList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="change"></el-date-picker>
+      </el-form-item>
+      <el-table-column label="完课时间" align="center" prop="finishTime" />
+      <el-table-column label="营期时间" align="center" prop="campPeriodTime" />
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['course:courseWatchLog:myExport']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-tabs type="card" v-model="queryParams.logType" @tab-click="handleClickX">
+      <el-tab-pane label="全部" name="10"></el-tab-pane>
+      <el-tab-pane v-for="(item,index) in logTypeOptions" :label="item.dictLabel" :name="item.dictValue"></el-tab-pane>
+    </el-tabs>
+    <el-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="记录编号" align="center" prop="logId" />
+      <el-table-column label="企微客户" align="center" prop="externalUserName"/>
+      <el-table-column label="会员ID" align="center" prop="userId" />
+      <el-table-column label="会员昵称" align="center" prop="fsNickName">
+        <template slot-scope="scope">
+          <div style="display: flex;white-space: nowrap">
+            <div style="margin: auto">
+              {{scope.row.fsNickName}}
+            </div>
+            <el-popover
+              placement="right"
+              title=""
+              trigger="hover">
+              <img slot="reference" :src="scope.row.fsAvatar" style="width: 30px;height: 30px">
+              <img :src="scope.row.fsAvatar" style="max-width: 200px;max-height: 200px">
+            </el-popover>
+          </div>
+        </template>
+      </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="logType">
+        <template slot-scope="scope">
+          <dict-tag :options="logTypeOptions" :value="scope.row.logType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="播放时长" align="center" prop="duration" />
+      <el-table-column label="所属销售" align="center" prop="companyUserName" />
+<!--      <el-table-column label="所属公司" align="center" prop="companyName" />-->
+      <el-table-column label="企微员工名称" align="center" prop="qwUserName" />
+      <el-table-column label="企微账号" align="center" prop="qwUserName" />
+<!--      <el-table-column label="所属发送方式" align="center" prop="sendType" />-->
+      <el-table-column label="创建时间" align="center" prop="createTime" />
+<!--      <el-table-column label="更新时间" align="center" prop="updateTime" />-->
+      <el-table-column label="完课时间" align="center" prop="lastHeartbeatTime" />
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import {
+  myListCourseWatchLog,
+  getCourseWatchLog,
+  delCourseWatchLog,
+  addCourseWatchLog,
+  updateCourseWatchLog,
+  exportCourseWatchLog,
+  exportCourseWatchLogMy
+} from "@/api/course/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import {getMyQwUserList} from "@/api/qw/user";
+import {allListTagGroup} from "@/api/qw/tagGroup";
+import {listTag} from "@/api/qw/tag";
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      createTime:null,
+      courseLists:[],
+      videoList:[],
+      myQwUserList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        videoId: null,
+        nickName:null,
+        logType: "10",
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+
+    getMyQwUserList().then(response => {
+      this.myQwUserList = response.data;
+    });
+  },
+  methods: {
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    updateQwuser(){
+      for (const user of this.myQwUserList) {
+        if (user.dictValue == this.queryParams.qwUserId) {
+          this.queryParams.corpId=user.corpId;
+          break;
+        }
+      }
+      this.getList();
+    },
+    change() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+
+    handleClickX(tab){
+      if(tab.name==="10"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      myListCourseWatchLog(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      };
+      this.scheduleTime=null;
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.scheduleTime=null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLogMy(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 318 - 0
src/views/course/courseWatchLog/qw/statistics.vue

@@ -0,0 +1,318 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="公司名" prop="companyId">
+        <el-select filterable style="width: 220px" v-model="queryParams.companyId" placeholder="请选择公司名" clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课程" prop="courseId">
+        <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
+          <el-option
+            v-for="dict in courseLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId">
+        <el-select filterable  v-model="queryParams.videoId" placeholder="请选择小节"  clearable size="small">
+          <el-option
+            v-for="dict in videoList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="企微昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入企微昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="创建时间" prop="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-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-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary>
+      <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"/>
+      <el-table-column label="课程名称" align="center" prop="courseName" />
+      <el-table-column label="小节名称" align="center" prop="videoName" />
+      <el-table-column label="待看课" align="center" prop="type3" />
+      <el-table-column label="看课中" align="center" prop="type1" />
+      <el-table-column label="已完课" align="center" prop="type2" />
+      <el-table-column label="看课中断" align="center" prop="type4" />
+
+
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,statisticsList } from "@/api/course/qw/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import {getCompanyList} from "@/api/company/company";
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      companys:[],
+      activeName:"00",
+      createTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: false,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+    };
+  },
+  created() {
+    getCompanyList().then(response => {
+      this.companys = response.data;
+      if(this.companys!=null&&this.companys.length>0){
+        this.companyId=this.companys[0].companyId;
+        this.getTreeselect();
+      }
+    });
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+  },
+  methods: {
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    change() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      statisticsList(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      if(this.queryParams.companyId == null) {
+        this.$message.warning("公司不能为空!")
+        return;
+      }
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLog(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+  }
+};
+</script>

+ 602 - 0
src/views/course/courseWatchLog/qw/watchLog.vue

@@ -0,0 +1,602 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="企微账号" prop="qwUserId">
+        <el-select v-model="queryParams.qwUserId" placeholder="企微账号" clearable size="small"
+                   @change="updateQwuser()">
+          <el-option
+            v-for="dict in myQwUserList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel + '('+dict.corpName+')'"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="客户ID" prop="qwExternalContactId">
+        <el-input
+          v-model="queryParams.qwExternalContactId"
+          placeholder="请输入会员ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="会员ID" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入会员ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="会员昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入会员昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="企微客户昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.externalUserName"
+          placeholder="请输入企微客户昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="课程" prop="courseId">
+        <el-select filterable v-model="queryParams.courseId" placeholder="请选择课程" clearable size="small"
+                   @change="courseChange(queryParams.courseId)">
+          <el-option
+            v-for="dict in courseLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId">
+        <el-select filterable v-model="queryParams.videoId" placeholder="请选择小节" clearable size="small">
+          <el-option
+            v-for="dict in videoList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="营期时间" prop="scheduleTime">
+        <el-date-picker
+          v-model="scheduleTime"
+          type="daterange"
+          size="small"
+          style="width: 240px"
+          value-format="yyyy-MM-dd"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          @change="handleScheduleTimeChange">
+        </el-date-picker>
+      </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="createChange"></el-date-picker>
+      </el-form-item>
+      <el-form-item label="最新更新时间" prop="updateTime">
+        <el-date-picker v-model="updateTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange"
+                        range-separator="-" start-placeholder="开始日期"
+                        end-placeholder="结束日期" @change="updateChange"></el-date-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['course:courseWatchLog:myExport']"
+        >导出
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-tabs type="card" v-model="activeName" @tab-click="handleClickX">
+      <el-tab-pane label="全部" name="00"></el-tab-pane>
+      <el-tab-pane v-for="(item,index) in logTypeOptions" :label="item.dictLabel" :name="item.dictValue"></el-tab-pane>
+    </el-tabs>
+    <el-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center"/>
+      <el-table-column label="记录编号" align="center" prop="logId"/>
+      <el-table-column label="企微客户" align="center" prop="externalUserName"/>
+      <el-table-column label="会员ID" align="center" prop="userId"/>
+      <el-table-column label="会员昵称" align="center" prop="fsNickName">
+        <template slot-scope="scope">
+          <div style="display: flex;white-space: nowrap">
+            <div style="margin: auto">
+              {{ scope.row.fsNickName }}
+            </div>
+            <el-popover
+              placement="right"
+              title=""
+              trigger="hover">
+              <img slot="reference" :src="scope.row.fsAvatar" style="width: 30px;height: 30px">
+              <img :src="scope.row.fsAvatar" style="max-width: 200px;max-height: 200px">
+            </el-popover>
+          </div>
+        </template>
+      </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="logType">
+        <template slot-scope="scope">
+          <dict-tag :options="logTypeOptions" :value="scope.row.logType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="播放时长" align="center" prop="duration"/>
+      <el-table-column label="所属销售" align="center" prop="companyUserName"/>
+<!--      <el-table-column label="所属公司" align="center" prop="companyName"/>-->
+      <el-table-column label="企微员工名称" align="center" prop="qwUserName"/>
+      <el-table-column label="企微账号" align="center" prop="qwUserName"/>
+<!--      <el-table-column label="所属发送方式" align="center" prop="sendType"/>-->
+      <el-table-column label="创建时间" align="center" prop="createTime" width="100px"/>
+      <el-table-column label="更新时间" align="center" prop="updateTime" width="100px" />
+      <el-table-column label="完课时间" align="center" prop="finishTime" width="100px" />
+      <el-table-column label="营期时间" align="center" prop="campPeriodTime" />
+      <el-table-column
+        fixed="right"
+        label="操作"
+        width="100">
+        <template slot-scope="scope">
+          <el-button @click="openAnswerLogFun(scope.row)" type="text" size="small">答题记录</el-button>
+          <el-button @click="openRedLogFun(scope.row)" type="text" size="small">红包记录</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-drawer title="做题记录" :visible.sync="openAnswerLog" size="70%" append-to-body>
+      <el-table border v-loading="" :data="answerLogsList">
+        <el-table-column label="小程序用户名" align="center" prop="userName">
+          <template slot-scope="scope">
+            <div style="display: flex;white-space: nowrap">
+              <div style="margin: auto">
+                {{ scope.row.userName }}
+              </div>
+              <el-popover
+                placement="right"
+                title=""
+                trigger="hover">
+                <img slot="reference" :src="scope.row.fsAvatar" style="width: 30px;height: 30px">
+                <img :src="scope.row.fsAvatar" style="max-width: 200px;max-height: 200px">
+              </el-popover>
+            </div>
+          </template>
+        </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="companyUserName"/>
+        <el-table-column label="企微员工名称" align="center" prop="qwUserName"/>
+        <el-table-column label="公司名称" align="center" prop="companyName"/>
+        <el-table-column label="创建时间" align="center" prop="createTime"/>
+      </el-table>
+
+      <pagination
+        v-show="answerLogTotal>0"
+        :total="answerLogTotal"
+        :page.sync="answerLogQueryParams.pageNum"
+        :limit.sync="answerLogQueryParams.pageSize"
+        @pagination="answerLogList"
+      />
+    </el-drawer>
+
+    <el-drawer title="红包记录" :visible.sync="openRedLog" size="70%" append-to-body>
+      <el-table border v-loading="" :data="redLogsList">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="记录编号" align="center" prop="logId" />
+        <el-table-column label="批次单号" align="center" prop="outBatchNo" />
+        <el-table-column label="课程名称" align="center" prop="courseId" >
+          <template slot-scope="scope">
+            <span prop="status" v-for="(item, index) in courseLists"    v-if="scope.row.courseId==item.dictValue">{{item.dictLabel}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="小节名称" align="center" prop="title" />
+        <el-table-column label="会员id" align="center" prop="userId" />
+        <el-table-column label="会员昵称" align="center" prop="fsNickName">
+          <template slot-scope="scope">
+            <div style="display: flex;white-space: nowrap">
+              <div style="margin: auto">
+                {{scope.row.fsNickName}}
+              </div>
+              <el-popover
+                placement="right"
+                title=""
+                trigger="hover">
+                <img slot="reference" :src="scope.row.fsAvatar" style="width: 30px;height: 30px">
+                <img :src="scope.row.fsAvatar" style="max-width: 200px;max-height: 200px">
+              </el-popover>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="会员电话" align="center" prop="phone" />
+        <el-table-column label="所属销售" align="center" prop="companyUserName" />
+        <el-table-column label="所属公司" align="center" prop="companyName" />
+        <el-table-column label="转帐金额" align="center" prop="amount" />
+        <el-table-column label="状态" align="center" prop="status" >
+          <template slot-scope="scope">
+            <el-tag>{{ scope.row.status === 0 ? "发送中" : "已完成" }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="企微员工名称" align="center" prop="qwUserName" />
+        <el-table-column label="创建时间" align="center" prop="createTime" />
+      </el-table>
+
+      <pagination
+        v-show="redLogTotal>0"
+        :total="redLogTotal"
+        :page.sync="redLogQueryParams.pageNum"
+        :limit.sync="redLogQueryParams.pageSize"
+        @pagination="redLogList"
+      />
+    </el-drawer>
+
+  </div>
+</template>
+
+<script>
+import {
+  addCourseWatchLog,
+  delCourseWatchLog,
+  exportCourseWatchLog, exportCourseWatchLogMy,
+  getCourseWatchLog,
+  myListCourseWatchLog,
+  updateCourseWatchLog
+} from "@/api/course/courseWatchLog";
+import {courseList, myListCourseRedPacketLog, videoList} from '@/api/course/courseRedPacketLog'
+import {myListLogs} from "@/api/course/courseAnswerlogs";
+import {getMyQwUserList} from "@/api/qw/user";
+
+
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      activeName:"00",
+      createTime: null,
+      updateTime:null,
+      courseLists: [],
+      videoList: [],
+      myQwUserList: [],
+      logTypeOptions: [],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      scheduleTime: null,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      sysCompanyOr: [],
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+
+
+      openAnswerLog: false,
+      loadingAnswerLog: true,
+      answerLogsList: [],
+      answerLogTotal: 0,
+      answerLogQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+      },
+
+
+      openRedLog: false,
+      loadingRedLog: true,
+      redLogsList: [],
+      redLogTotal: 0,
+      redLogQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+      },
+
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        videoId: null,
+        nickName: null,
+        logType: "10",
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime: null,
+        eTime: null,
+        upSTime:null,
+        upETime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {}
+    };
+  },
+  created() {
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+
+    this.getDicts("sys_company_or").then(response => {
+      this.sysCompanyOr = response.data;
+    });
+    getMyQwUserList().then(response => {
+      this.myQwUserList = response.data;
+    });
+  },
+  methods: {
+    courseChange(row) {
+      this.queryParams.videoId = null;
+      if (row === '') {
+        this.videoList = [];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList = response.list
+      });
+    },
+    updateQwuser() {
+      for (const user of this.myQwUserList) {
+        if (user.dictValue == this.queryParams.qwUserId) {
+          this.queryParams.corpId = user.corpId;
+          break;
+        }
+      }
+      this.getList();
+    },
+    createChange() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    updateChange(){
+      if (this.updateTime != null) {
+        this.queryParams.upSTime = this.updateTime[0];
+        this.queryParams.upETime = this.updateTime[1];
+      } else {
+        this.queryParams.upSTime = null;
+        this.queryParams.upETime = null;
+      }
+    },
+    handleClickX(tab) {
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      let param = JSON.parse(JSON.stringify(this.queryParams));
+      if (param.logType == "10") {
+        param.logType = null;
+      }
+      myListCourseWatchLog(param).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      };
+      this.scheduleTime=null;
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.upSTime = null;
+      this.queryParams.upETime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.scheduleTime=null;
+      this.updateTime=null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delCourseWatchLog(logIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportCourseWatchLogMy(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+      });
+    },
+    openAnswerLogFun(row) {
+      this.openAnswerLog = true;
+      this.answerLogQueryParams.watchLogId = row.logId;
+      this.answerLogList();
+    },
+    answerLogList() {
+      this.loadingAnswerLog = true;
+      myListLogs(this.answerLogQueryParams).then(e => {
+        this.answerLogsList = e.rows;
+        this.answerLogTotal = e.total;
+        this.loadingAnswerLog = false;
+      })
+    },
+    openRedLogFun(row) {
+      this.openRedLog = true;
+      this.redLogQueryParams.watchLogId = row.logId;
+      this.redLogList();
+    },
+    redLogList() {
+      this.loadingRedLog = true;
+      console.info(this.redLogQueryParams)
+      myListCourseRedPacketLog(this.redLogQueryParams).then(e => {
+        this.redLogsList = e.rows;
+        this.redLogTotal = e.total;
+        this.loadingRedLog = false;
+      })
+    },
+  }
+};
+</script>

+ 314 - 0
src/views/course/courseWatchLog/qw/watchLogStatistics.vue

@@ -0,0 +1,314 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+
+      <el-form-item label="企微昵称" prop="nickName" >
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入企微昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+
+      <el-form-item label="创建时间" prop="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="createChange"></el-date-picker>
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange">
+      <el-table-column label="企微客户" align="center" prop="externalUserName"/>
+<!--      <el-table-column label="所属销售" align="center" prop="companyUserName" /> -->
+      <el-table-column label="企微员工名称" align="center" prop="qwUserName" />
+      <el-table-column label="创建时间" align="center" prop="createTime" />
+      <el-table-column label="进线时间" align="center" prop="userCreateTime" />
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,watchLogStatistics,watchLogStatisticsExport } from "@/api/course/qw/courseWatchLog";
+import {allList}from "@/api/company/company";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      activeName:"00",
+      createTime:null,
+      updateTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        upSTime:null,
+        upETime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+    };
+  },
+  created() {
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+  },
+  methods: {
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    createChange() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+
+    updateChange(){
+      if (this.updateTime != null) {
+        this.queryParams.upSTime = this.updateTime[0];
+        this.queryParams.upETime = this.updateTime[1];
+      } else {
+        this.queryParams.upSTime = null;
+        this.queryParams.upETime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      if(this.queryParams.logType == "10"){
+        this.queryParams.logType = null;
+      }
+
+      watchLogStatistics(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      };
+      this.scheduleTime=null;
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.upSTime = null;
+      this.queryParams.upETime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.scheduleTime=null;
+      this.updateTime=null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return watchLogStatisticsExport(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+  }
+};
+</script>

+ 390 - 0
src/views/course/courseWatchLog/statistics.vue

@@ -0,0 +1,390 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="用户" prop="nickName">
+        <el-select v-model="queryParams.userId" remote placeholder="用户名/手机号" filterable clearable  style="width: 100%;"
+                   @keyup.enter.native="handleQuery"
+                   :remote-method="remoteGetFsUserList"
+                   @clear="handleClear"
+                   :loading="queryUserLoading"
+        >
+          <el-option
+            v-for="dict in fsUserList"
+            :key="`${dict.nickname} - ${dict.phone}`"
+            :label="`${dict.nickname} - ${dict.phone}`"
+            :value="dict.userId">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="公司名" prop="companyId">
+        <el-select filterable style="width: 220px" v-model="queryParams.companyId" @change="handleSeller" placeholder="请选择公司名" clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="销售" prop="nickName" v-if="queryParams.companyId">
+        <el-select v-model="queryParams.companyUserId" remote placeholder="请选择" filterable clearable  style="width: 100%;" @keyup.enter.native="handleQuery">
+          <el-option
+            v-for="dict in companyUserList"
+            :key="`${dict.nickName} - ${dict.userName}`"
+            :label="`${dict.nickName} - ${dict.userName}`"
+            :value="dict.userId">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="项目" prop="courseId">
+        <el-select filterable  v-model="queryParams.project" placeholder="请选择项目"  clearable size="small">
+          <el-option
+            v-for="dict in projectLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="parseInt(dict.dictValue)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课程" prop="courseId">
+        <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
+          <el-option
+            v-for="dict in courseLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId">
+        <el-select filterable  v-model="queryParams.videoId" placeholder="请选择小节"  clearable size="small">
+          <el-option
+            v-for="dict in videoList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="change"></el-date-picker>
+      </el-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-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange"  show-summary>
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="用户" align="center" prop="userName" />
+      <el-table-column label="对应销售" align="center" prop="companyUserName" />
+      <el-table-column label="发课时间" align="center" prop="createTime"/>
+      <el-table-column label="项目" align="center" prop="projectName" />
+      <el-table-column label="课程名称" align="center" prop="courseName" />
+      <el-table-column label="小节名称" align="center" prop="videoName" />
+      <el-table-column label="待看课" align="center" prop="type3" />
+      <el-table-column label="看课中" align="center" prop="type1" />
+      <el-table-column label="已完课" align="center" prop="type2" />
+      <el-table-column label="看课中断" align="center" prop="type4" />
+    </el-table>
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,statisticsList } from "@/api/course/courseWatchLog";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import {getUserList} from "@/api/company/companyUser";
+import {getFsUserList} from "@/api/users/user";
+import {getCompanyList} from "@/api/company/company";
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      companys:[],
+      activeName:"00",
+      createTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: false,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      companyUserList: [],
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      projectLists: [],
+      fsUserList: [],
+      queryUserLoading: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+    };
+  },
+  created() {
+    getCompanyList().then(response => {
+      this.companys = response.data;
+      if(this.companys!=null&&this.companys.length>0){
+        this.companyId=this.companys[0].companyId;
+        this.getTreeselect();
+      }
+    });
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+    this.getDicts("sys_course_project").then(response => {
+      this.projectLists = response.data;
+    })
+
+  },
+  methods: {
+    handleSeller(){
+      console.log(this.queryParams.companyId)
+      if(this.queryParams.companyId != null) {
+        getUserList(this.queryParams.companyId).then(res=>{
+          if(res.code === 200) {
+            this.companyUserList = res.data
+          }
+        })
+      }
+    },
+    handleClear(){
+      this.queryUserLoading = false;
+      this.fsUserList = [];
+    },
+    remoteGetFsUserList(query){
+      if(query){
+        this.queryUserLoading = true;
+        const isNumeric = /^\d+$/.test(query);
+        let payload = {
+          username: query,
+          nickname: query
+        }
+        if(isNumeric) {
+          payload = {
+            userId: query,
+            username: query,
+            phone: query
+          }
+        }
+        getFsUserList(payload).then(res=>{
+          if(res.code === 200) {
+            this.fsUserList = res.data
+          }
+        }).finally(()=>{
+          this.queryUserLoading = false;
+        })
+      }
+    },
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    change() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      statisticsList(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+
+      }).finally(()=>{
+        this.loading = false;
+      })
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      if(this.queryParams.companyId == null){
+        this.$message.error("请选择公司!");
+        return;
+      }
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCourseWatchLog(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+  }
+};
+</script>

+ 43 - 0
src/views/course/courseWatchLog/statisticsTabIndex.vue

@@ -0,0 +1,43 @@
+<!-- TabComponent.vue -->
+<template>
+  <div class="tab-container">
+    <el-tabs type="card" style="background-color: white">
+      <el-tab-pane label="会员">
+        <member-view/>
+      </el-tab-pane>
+      <el-tab-pane label="企微">
+        <qw-view/>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import MemberView from './statistics.vue';
+import QwView from './qw/statistics.vue';
+
+
+export default {
+  name: 'TabComponent',
+  components: {
+    MemberView,
+    QwView
+  },
+  data() {
+    return {
+    };
+  }
+};
+</script>
+
+<style scoped>
+.tab-container {
+  width: 100%;
+  height: 100%;
+}
+.app-container{
+  padding: 15px !important;
+  margin: 0px !important;
+  background-color: #fff !important;
+}
+</style>

+ 631 - 0
src/views/course/courseWatchLog/watchLog.vue

@@ -0,0 +1,631 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="用户" prop="qwUserId">
+        <el-select v-model="queryParams.userId" remote placeholder="用户名/手机号" filterable clearable  style="width: 100%;"
+                   @keyup.enter.native="handleQuery"
+                   :remote-method="remoteGetFsUserList"
+                   @clear="handleClear"
+                   :loading="queryUserLoading"
+        >
+          <el-option
+            v-for="dict in fsUserList"
+            :key="`${dict.nickname} - ${dict.phone}`"
+            :label="`${dict.nickname} - ${dict.phone}`"
+            :value="dict.userId">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课程" prop="courseId">
+        <el-select filterable v-model="queryParams.courseId" placeholder="请选择课程" clearable size="small"
+                   @change="courseChange(queryParams.courseId)">
+          <el-option
+            v-for="dict in courseLists"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId">
+        <el-select filterable v-model="queryParams.videoId" placeholder="请选择小节" clearable size="small">
+          <el-option
+            v-for="dict in videoList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="营期时间" prop="scheduleTime">
+        <el-date-picker
+          v-model="scheduleTime"
+          type="daterange"
+          size="small"
+          style="width: 240px"
+          value-format="yyyy-MM-dd"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          @change="handleScheduleTimeChange">
+        </el-date-picker>
+      </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="createChange"></el-date-picker>
+      </el-form-item>
+      <el-form-item label="最新更新时间" prop="updateTime">
+        <el-date-picker v-model="updateTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange"
+                        range-separator="-" start-placeholder="开始日期"
+                        end-placeholder="结束日期" @change="updateChange"></el-date-picker>
+      </el-form-item>
+
+      <el-form-item label="类型" prop="type">
+        <el-select filterable  v-model="sourceTypeModel" placeholder="请选择小节"  clearable size="small">
+          <el-option
+            v-for="dict in userSourceTypeOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['course:courseWatchLog:myExport']"
+        >导出
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-tabs type="card" v-model="activeName" @tab-click="handleClickX">
+      <el-tab-pane label="全部" name="00"></el-tab-pane>
+      <el-tab-pane v-for="(item,index) in logTypeOptions" :label="item.dictLabel" :name="item.dictValue"></el-tab-pane>
+    </el-tabs>
+    <el-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center"/>
+      <el-table-column label="记录编号" align="center" prop="logId"/>
+<!--      <el-table-column label="企微客户" align="center" prop="externalUserName"/>-->
+      <el-table-column label="用户账号" align="center" prop="userName" />
+      <el-table-column label="企微客户" align="center" prop="externalUserName" v-if="queryParams.sourceType == 2"/>
+
+      <el-table-column label="会员昵称" align="center" prop="fsNickName">
+        <template slot-scope="scope">
+          <div style="display: flex;white-space: nowrap">
+            <div style="margin: auto">
+              {{ scope.row.fsNickName }}
+            </div>
+            <el-popover
+              placement="right"
+              title=""
+              trigger="hover">
+              <img slot="reference" :src="scope.row.fsAvatar" style="width: 30px;height: 30px">
+              <img :src="scope.row.fsAvatar" style="max-width: 200px;max-height: 200px">
+            </el-popover>
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column label="项目" align="center" prop="projectName" />
+      <el-table-column label="课程名称" align="center" prop="courseName"/>
+      <el-table-column label="小节名称" align="center" prop="videoName"/>
+      <el-table-column label="记录类型" align="center" prop="logType">
+        <template slot-scope="scope">
+          <dict-tag :options="logTypeOptions" :value="scope.row.logType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="播放时长" align="center" prop="duration"/>
+      <el-table-column label="所属销售" align="center" prop="companyUserName"/>
+<!--      <el-table-column label="所属公司" align="center" prop="companyName"/>-->
+<!--      <el-table-column label="企微员工名称" align="center" prop="qwUserName"/>-->
+<!--      <el-table-column label="企微账号" align="center" prop="qwUserName"/>-->
+<!--      <el-table-column label="所属发送方式" align="center" prop="sendType"/>-->
+      <el-table-column label="企微员工名称" align="center" prop="qwUserName" v-if="queryParams.sourceType == 2"/>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="100px"/>
+      <el-table-column label="更新时间" align="center" prop="updateTime" width="100px" />
+      <el-table-column label="完课时间" align="center" prop="finishTime" width="100px" />
+      <el-table-column label="营期时间" align="center" prop="campPeriodTime" />
+      <el-table-column
+        fixed="right"
+        label="操作"
+        width="100">
+        <template slot-scope="scope">
+          <el-button @click="openAnswerLogFun(scope.row)" type="text" size="small">答题记录</el-button>
+          <el-button @click="openRedLogFun(scope.row)" type="text" size="small">红包记录</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-drawer title="做题记录" :visible.sync="openAnswerLog" size="70%" append-to-body>
+      <el-table border v-loading="" :data="answerLogsList">
+        <el-table-column label="小程序用户名" align="center" prop="userName">
+          <template slot-scope="scope">
+            <div style="display: flex;white-space: nowrap">
+              <div style="margin: auto">
+                {{ scope.row.userName }}
+              </div>
+              <el-popover
+                placement="right"
+                title=""
+                trigger="hover">
+                <img slot="reference" :src="scope.row.fsAvatar" style="width: 30px;height: 30px">
+                <img :src="scope.row.fsAvatar" style="max-width: 200px;max-height: 200px">
+              </el-popover>
+            </div>
+          </template>
+        </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="companyUserName"/>
+        <el-table-column label="企微员工名称" align="center" prop="qwUserName"/>
+        <el-table-column label="公司名称" align="center" prop="companyName"/>
+        <el-table-column label="创建时间" align="center" prop="createTime"/>
+      </el-table>
+
+      <pagination
+        v-show="answerLogTotal>0"
+        :total="answerLogTotal"
+        :page.sync="answerLogQueryParams.pageNum"
+        :limit.sync="answerLogQueryParams.pageSize"
+        @pagination="answerLogList"
+      />
+    </el-drawer>
+
+    <el-drawer title="红包记录" :visible.sync="openRedLog" size="70%" append-to-body>
+      <el-table border v-loading="" :data="redLogsList">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="记录编号" align="center" prop="logId" />
+        <el-table-column label="批次单号" align="center" prop="outBatchNo" />
+        <el-table-column label="课程名称" align="center" prop="courseId" >
+          <template slot-scope="scope">
+            <span prop="status" v-for="(item, index) in courseLists"    v-if="scope.row.courseId==item.dictValue">{{item.dictLabel}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="小节名称" align="center" prop="title" />
+        <el-table-column label="会员id" align="center" prop="userId" />
+        <el-table-column label="会员昵称" align="center" prop="fsNickName">
+          <template slot-scope="scope">
+            <div style="display: flex;white-space: nowrap">
+              <div style="margin: auto">
+                {{scope.row.fsNickName}}
+              </div>
+              <el-popover
+                placement="right"
+                title=""
+                trigger="hover">
+                <img slot="reference" :src="scope.row.fsAvatar" style="width: 30px;height: 30px">
+                <img :src="scope.row.fsAvatar" style="max-width: 200px;max-height: 200px">
+              </el-popover>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="会员电话" align="center" prop="phone" />
+        <el-table-column label="所属销售" align="center" prop="companyUserName" />
+        <el-table-column label="所属公司" align="center" prop="companyName" />
+        <el-table-column label="转帐金额" align="center" prop="amount" />
+        <el-table-column label="状态" align="center" prop="status" >
+          <template slot-scope="scope">
+            <el-tag>{{ scope.row.status === 0 ? "发送中" : "已完成" }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="企微员工名称" align="center" prop="qwUserName" />
+        <el-table-column label="创建时间" align="center" prop="createTime" />
+      </el-table>
+
+      <pagination
+        v-show="redLogTotal>0"
+        :total="redLogTotal"
+        :page.sync="redLogQueryParams.pageNum"
+        :limit.sync="redLogQueryParams.pageSize"
+        @pagination="redLogList"
+      />
+    </el-drawer>
+
+  </div>
+</template>
+
+<script>
+import {
+  addCourseWatchLog,
+  delCourseWatchLog,
+  exportCourseWatchLog,
+  getCourseWatchLog,
+  myListCourseWatchLog,
+  updateCourseWatchLog
+} from "@/api/course/courseWatchLog";
+import {courseList, myListCourseRedPacketLog, videoList} from '@/api/course/courseRedPacketLog'
+import {myListLogs} from "@/api/course/courseAnswerlogs";
+import {getMyQwUserList} from "@/api/qw/user";
+import {getFsUserList} from "@/api/users/user";
+
+
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      userSourceTypeOptions: [],
+      activeName:"00",
+      createTime: null,
+      updateTime:null,
+      courseLists: [],
+      videoList: [],
+      myQwUserList: [],
+      logTypeOptions: [],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      queryUserLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      scheduleTime: null,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      sysCompanyOr: [],
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      fsUserList: [],
+
+      openAnswerLog: false,
+      loadingAnswerLog: true,
+      answerLogsList: [],
+      answerLogTotal: 0,
+      answerLogQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+      },
+
+
+      openRedLog: false,
+      loadingRedLog: true,
+      redLogsList: [],
+      redLogTotal: 0,
+      redLogQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+      },
+
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        videoId: null,
+        nickName: null,
+        logType: "10",
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime: null,
+        eTime: null,
+        upSTime:null,
+        upETime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+        sourceType: 1
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {}
+    };
+  },
+  computed: {
+    sourceTypeModel: {
+      get() {
+        return this.queryParams.sourceType !== null && this.queryParams.sourceType !== undefined ? this.queryParams.sourceType.toString() : null;
+      },
+      set(newVal) {
+        this.queryParams.sourceType = newVal;
+      }
+    }
+  },
+  created() {
+    this.getDicts('user_source_type').then(response => {
+      this.userSourceTypeOptions = response.data;
+    })
+
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+
+    this.getDicts("sys_company_or").then(response => {
+      this.sysCompanyOr = response.data;
+    });
+    getMyQwUserList().then(response => {
+      this.myQwUserList = response.data;
+    });
+  },
+  methods: {
+    handleClear(){
+      this.queryUserLoading = false;
+      this.fsUserList = [];
+    },
+    remoteGetFsUserList(query){
+      if(query){
+        this.queryUserLoading = true;
+        const isNumeric = /^\d+$/.test(query);
+        let payload = {
+          username: query,
+          nickname: query
+        }
+        if(isNumeric) {
+          payload = {
+            userId: query,
+            username: query,
+            phone: query
+          }
+        }
+        getFsUserList(payload).then(res=>{
+          if(res.code === 200) {
+            this.fsUserList = res.data
+          }
+        }).finally(()=>{
+          this.queryUserLoading = false;
+        })
+      }
+    },
+    courseChange(row) {
+      this.queryParams.videoId = null;
+      if (row === '') {
+        this.videoList = [];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList = response.list
+      });
+    },
+    updateQwuser() {
+      for (const user of this.myQwUserList) {
+        if (user.dictValue == this.queryParams.qwUserId) {
+          this.queryParams.corpId = user.corpId;
+          break;
+        }
+      }
+      this.getList();
+    },
+    createChange() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    updateChange(){
+      if (this.updateTime != null) {
+        this.queryParams.upSTime = this.updateTime[0];
+        this.queryParams.upETime = this.updateTime[1];
+      } else {
+        this.queryParams.upSTime = null;
+        this.queryParams.upETime = null;
+      }
+    },
+    handleClickX(tab) {
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      let param = JSON.parse(JSON.stringify(this.queryParams));
+      if (param.logType == "10") {
+        param.logType = null;
+      }
+      myListCourseWatchLog(param).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      };
+      this.scheduleTime=null;
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.upSTime = null;
+      this.queryParams.upETime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.scheduleTime=null;
+      this.updateTime=null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delCourseWatchLog(logIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportCourseWatchLog(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+      });
+    },
+    openAnswerLogFun(row) {
+      this.openAnswerLog = true;
+      this.answerLogQueryParams.watchLogId = row.logId;
+      this.answerLogList();
+    },
+    answerLogList() {
+      this.loadingAnswerLog = true;
+      myListLogs(this.answerLogQueryParams).then(e => {
+        this.answerLogsList = e.rows;
+        this.answerLogTotal = e.total;
+        this.loadingAnswerLog = false;
+      })
+    },
+    openRedLogFun(row) {
+      this.openRedLog = true;
+      this.redLogQueryParams.watchLogId = row.logId;
+      this.redLogList();
+    },
+    redLogList() {
+      this.loadingRedLog = true;
+      console.info(this.redLogQueryParams)
+      myListCourseRedPacketLog(this.redLogQueryParams).then(e => {
+        this.redLogsList = e.rows;
+        this.redLogTotal = e.total;
+        this.loadingRedLog = false;
+      })
+    },
+  }
+};
+</script>

+ 321 - 0
src/views/course/courseWatchLog/watchLogStatistics.vue

@@ -0,0 +1,321 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+
+      <el-form-item label="销售用户名" prop="nickName" >
+        <el-select v-model="queryParams.companyUserId" remote placeholder="请选择" filterable clearable  style="width: 100%;" @keyup.enter.native="handleQuery">
+          <el-option
+            v-for="dict in companyUserList"
+            :key="`${dict.nickName} - ${dict.userName}`"
+            :label="`${dict.nickName} - ${dict.userName}`"
+            :value="dict.userId">
+          </el-option>
+        </el-select>
+      </el-form-item>
+
+
+      <el-form-item label="创建时间" prop="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="createChange"></el-date-picker>
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="courseWatchLogList" @selection-change="handleSelectionChange">
+      <el-table-column label="销售名称" align="center" prop="userName"/>
+<!--      <el-table-column label="所属销售" align="center" prop="companyUserName" /> -->
+      <el-table-column label="创建时间" align="center" prop="createTime" />
+      <el-table-column label="进线时间" align="center" prop="userCreateTime" />
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,watchLogStatistics,watchLogStatisticsExport } from "@/api/course/courseWatchLog";
+import {allList}from "@/api/company/company";
+import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import {getUserList} from "@/api/company/companyUser";
+export default {
+  name: "CourseWatchLog",
+  data() {
+    return {
+      activeName:"00",
+      createTime:null,
+      updateTime:null,
+      courseLists:[],
+      videoList:[],
+      logTypeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 短链课程看课记录表格数据
+      courseWatchLogList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        nickName: null,
+        videoId: null,
+        logType: null,
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        sTime:null,
+        eTime:null,
+        upSTime:null,
+        upETime:null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      },
+      companyUserList: [],
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+      scheduleTime: null,
+    };
+  },
+  created() {
+    courseList().then(response => {
+      this.courseLists = response.list;
+    });
+    this.getList();
+    this.getDicts("sys_course_watch_log_type").then(response => {
+      this.logTypeOptions = response.data;
+    });
+    getUserList().then(res=>{
+      if(res.code === 200) {
+        this.companyUserList = res.data
+      }
+    })
+  },
+  methods: {
+    courseChange(row){
+      this.queryParams.videoId=null;
+      if(row === ''){
+        this.videoList=[];
+        return
+      }
+      videoList(row).then(response => {
+        this.videoList=response.list
+      });
+    },
+    createChange() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+
+    updateChange(){
+      if (this.updateTime != null) {
+        this.queryParams.upSTime = this.updateTime[0];
+        this.queryParams.upETime = this.updateTime[1];
+      } else {
+        this.queryParams.upSTime = null;
+        this.queryParams.upETime = null;
+      }
+    },
+    handleClickX(tab,event){
+      this.activeName=tab.name;
+      if(tab.name=="00"){
+        this.queryParams.logType=null;
+      }else{
+        this.queryParams.logType=tab.name;
+      }
+      this.getList()
+    },
+    /** 查询短链课程看课记录列表 */
+    getList() {
+      this.loading = true;
+      if(this.queryParams.logType == "10"){
+        this.queryParams.logType = null;
+      }
+
+      watchLogStatistics(this.queryParams).then(response => {
+        this.courseWatchLogList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        logId: null,
+        userId: null,
+        videoId: null,
+        logType: null,
+        createTime: null,
+        updateTime: null,
+        qwExternalContactId: null,
+        externalUserName:null,
+        duration: null,
+        qwUserId: null,
+        companyUserId: null,
+        companyId: null,
+        courseId: null,
+        scheduleStartTime: null,
+        scheduleEndTime: null,
+      };
+      this.scheduleTime=null;
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.scheduleTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.queryParams.upSTime = null;
+      this.queryParams.upETime = null;
+      this.queryParams.scheduleStartTime = null;
+      this.queryParams.scheduleEndTime = null;
+      this.scheduleTime=null;
+      this.updateTime=null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.logId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加短链课程看课记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const logId = row.logId || this.ids
+      getCourseWatchLog(logId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改短链课程看课记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.logId != null) {
+            updateCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCourseWatchLog(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const logIds = row.logId || this.ids;
+      this.$confirm('是否确认删除短链课程看课记录编号为"' + logIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCourseWatchLog(logIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return watchLogStatisticsExport(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    handleScheduleTimeChange(val) {
+      if (val) {
+        this.queryParams.scheduleStartTime = val[0];
+        this.queryParams.scheduleEndTime = val[1];
+      } else {
+        this.queryParams.scheduleStartTime = null;
+        this.queryParams.scheduleEndTime = null;
+      }
+    },
+  }
+};
+</script>

+ 43 - 0
src/views/course/courseWatchLog/watchLogStatisticsTabIndex.vue

@@ -0,0 +1,43 @@
+<!-- TabComponent.vue -->
+<template>
+  <div class="tab-container">
+    <el-tabs type="card" style="background-color: white">
+      <el-tab-pane label="会员">
+        <member-view/>
+      </el-tab-pane>
+      <el-tab-pane label="企微">
+        <qw-view/>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import MemberView from './watchLogStatistics.vue';
+import QwView from './qw/watchLogStatistics.vue';
+
+
+export default {
+  name: 'TabComponent',
+  components: {
+    MemberView,
+    QwView
+  },
+  data() {
+    return {
+    };
+  }
+};
+</script>
+
+<style scoped>
+.tab-container {
+  width: 100%;
+  height: 100%;
+}
+.app-container{
+  padding: 15px !important;
+  margin: 0px !important;
+  background-color: #fff !important;
+}
+</style>

+ 43 - 0
src/views/course/courseWatchLog/watchLogTabIndex.vue

@@ -0,0 +1,43 @@
+<!-- TabComponent.vue -->
+<template>
+  <div class="tab-container">
+    <el-tabs type="card" style="background-color: white">
+      <el-tab-pane label="会员">
+        <member-view/>
+      </el-tab-pane>
+      <el-tab-pane label="企微">
+        <qw-view/>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import MemberView from './index.vue';
+import QwView from './qw/index.vue';
+
+
+export default {
+  name: 'TabComponent',
+  components: {
+    MemberView,
+    QwView
+  },
+  data() {
+    return {
+    };
+  }
+};
+</script>
+
+<style scoped>
+.tab-container {
+  width: 100%;
+  height: 100%;
+}
+.app-container{
+  padding: 15px !important;
+  margin: 0px !important;
+  background-color: #fff !important;
+}
+</style>

+ 226 - 379
src/views/course/userCourse/index.vue

@@ -2,23 +2,24 @@
   <div class="app-container">
     <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
       <el-form-item label="课堂分类" prop="cateId">
-        <el-select v-model="queryParams.cateId" placeholder="请选择" clearable size="small"  @change="getQuerySubCateList(queryParams.cateId)">
+        <el-select v-model="queryParams.cateId" placeholder="请选择" clearable size="small"
+                   @change="getQuerySubCateList(queryParams.cateId)">
           <el-option
-              v-for="dict in categoryOptions"
-              :key="dict.dictValue"
-              :label="dict.dictLabel"
-              :value="dict.dictValue"
-            />
+            v-for="dict in categoryOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
         </el-select>
       </el-form-item>
       <el-form-item label="课堂子分类" prop="subCateId">
         <el-select v-model="queryParams.subCateId" placeholder="请选择" clearable size="small">
           <el-option
-              v-for="dict in querySubCateOptions"
-              :key="dict.dictValue"
-              :label="dict.dictLabel"
-              :value="dict.dictValue"
-            />
+            v-for="dict in querySubCateOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
         </el-select>
       </el-form-item>
       <el-form-item label="课堂名称" prop="courseName">
@@ -30,14 +31,14 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="课堂类型" prop="isPrivate">
+      <el-form-item label="课堂类型" prop="isPrivate" style="display: none">
         <el-select v-model="queryParams.isPrivate" placeholder="请选择" clearable size="small">
           <el-option
-              v-for="dict in courseTypeOptions"
-              :key="dict.dictValue"
-              :label="dict.dictLabel"
-              :value="dict.dictValue"
-            />
+            v-for="dict in courseTypeOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
         </el-select>
       </el-form-item>
       <el-form-item>
@@ -55,7 +56,8 @@
           size="mini"
           @click="handleAdd"
           v-hasPermi="['course:userCourse:add']"
-        >新增</el-button>
+        >新增
+        </el-button>
       </el-col>
       <el-col :span="1.5">
         <el-button
@@ -66,7 +68,8 @@
           :disabled="single"
           @click="handleUpdate"
           v-hasPermi="['course:userCourse:edit']"
-        >修改</el-button>
+        >修改
+        </el-button>
       </el-col>
       <el-col :span="1.5">
         <el-button
@@ -77,7 +80,8 @@
           :disabled="multiple"
           @click="handleDelete"
           v-hasPermi="['course:userCourse:remove']"
-        >删除</el-button>
+        >删除
+        </el-button>
       </el-col>
       <el-col :span="1.5">
         <el-button
@@ -88,11 +92,12 @@
           :loading="exportLoading"
           @click="handleExport"
           v-hasPermi="['course:userCourse:export']"
-        >导出</el-button>
+        >导出
+        </el-button>
       </el-col>
       <el-col :span="1.5">
         <el-button
-        v-if="queryParams.isShow==0"
+          v-if="queryParams.isShow==0"
           type="success"
           plain
           icon="el-icon-edit"
@@ -100,7 +105,8 @@
           :disabled="multiple"
           @click="putOn"
           v-hasPermi="['course:userCourse:putOn']"
-        >上架</el-button>
+        >上架
+        </el-button>
       </el-col>
       <el-col :span="1.5">
         <el-button
@@ -112,7 +118,8 @@
           :disabled="multiple"
           @click="pullOff"
           v-hasPermi="['course:userCourse:pullOff']"
-        >下架</el-button>
+        >下架
+        </el-button>
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
@@ -122,9 +129,9 @@
       <el-tab-pane label="待上架" name="0"></el-tab-pane>
     </el-tabs>
     <el-table height="600" border v-loading="loading" :data="userCourseList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="课程ID" align="center" prop="courseId" />
-      <el-table-column label="所属项目" align="center" prop="projectName" />
+      <el-table-column type="selection" width="55" align="center"/>
+      <el-table-column label="课程ID" align="center" prop="courseId"/>
+      <el-table-column label="所属项目" align="center" prop="projectName"/>
       <el-table-column label="封面图片" align="center" prop="imgUrl" width="120">
         <template slot-scope="scope">
           <el-popover
@@ -132,37 +139,23 @@
             title=""
             trigger="hover"
           >
-            <img slot="reference" :src="scope.row.imgUrl" width="100" >
+            <img slot="reference" :src="scope.row.imgUrl" width="100">
             <img :src="scope.row.imgUrl" style="max-width: 300px;">
           </el-popover>
         </template>
       </el-table-column>
-      <el-table-column label="小封面" align="center" prop="secondImg" width="100">
-        <template slot-scope="scope">
-          <el-popover
-            placement="right"
-            title=""
-            trigger="hover"
-          >
-            <img slot="reference" :src="scope.row.secondImg" width="50">
-            <img :src="scope.row.secondImg" style="max-width: 100px;">
-          </el-popover>
-        </template>
-      </el-table-column>
-      <el-table-column label="课堂名称" align="center" show-overflow-tooltip prop="courseName" />
-      <el-table-column label="排序" align="center" prop="sort" />
-      <el-table-column label="分类名称" align="center" prop="cateName" />
-      <el-table-column label="子分类名称" align="center" prop="subCateName" />
-      <el-table-column label="原价" align="center" prop="price" />
-      <el-table-column label="售价" align="center" prop="sellPrice" />
-      <el-table-column label="单节积分" align="center" prop="integral" />
+      <el-table-column label="课堂名称" align="center" show-overflow-tooltip prop="courseName"/>
+      <el-table-column label="排序" align="center" prop="sort"/>
+      <el-table-column label="分类名称" align="center" prop="cateName"/>
+      <el-table-column label="子分类名称" align="center" prop="subCateName"/>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
           <el-button
             size="mini"
             type="text"
             @click="handleCatalog(scope.row)"
-          >目录管理</el-button>
+          >目录管理
+          </el-button>
           <el-button
             size="mini"
             type="text"
@@ -178,14 +171,16 @@
             icon="el-icon-edit"
             @click="handleUpdate(scope.row)"
             v-hasPermi="['course:userCourse:edit']"
-          >修改</el-button>
+          >修改
+          </el-button>
           <el-button
             size="mini"
             type="text"
             icon="el-icon-delete"
             @click="handleDelete(scope.row)"
             v-hasPermi="['course:userCourse:remove']"
-          >删除</el-button>
+          >删除
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -213,21 +208,22 @@
             </el-select>
           </el-form-item>
         </el-row>
-        <el-row >
+        <el-row>
           <el-col :span="8">
             <el-form-item label="课堂名称" prop="courseName">
-              <el-input v-model="form.courseName" placeholder="请输入课堂名称" />
+              <el-input v-model="form.courseName" placeholder="请输入课堂名称"/>
             </el-form-item>
           </el-col>
           <el-col :span="8">
             <el-form-item label="课堂分类" prop="cateId">
-              <el-select v-model="form.cateId" placeholder="请选择" clearable size="small"  @change="getSubCateList(form.cateId)">
+              <el-select v-model="form.cateId" placeholder="请选择" clearable size="small"
+                         @change="getSubCateList(form.cateId)">
                 <el-option
-                    v-for="dict in categoryOptions"
-                    :key="dict.dictValue"
-                    :label="dict.dictLabel"
-                    :value="dict.dictValue"
-                  />
+                  v-for="dict in categoryOptions"
+                  :key="dict.dictValue"
+                  :label="dict.dictLabel"
+                  :value="dict.dictValue"
+                />
               </el-select>
             </el-form-item>
           </el-col>
@@ -235,63 +231,7 @@
             <el-form-item label="课堂子分类" prop="subCateId">
               <el-select v-model="form.subCateId" placeholder="请选择" clearable size="small">
                 <el-option
-                    v-for="dict in subCategoryOptions"
-                    :key="dict.dictValue"
-                    :label="dict.dictLabel"
-                    :value="dict.dictValue"
-                  />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <!-- <el-col :span="8">
-            <el-form-item label="课堂类型" prop="courseType">
-              <el-select v-model="form.courseType" placeholder="请选择" clearable size="small">
-                <el-option
-                    v-for="dict in courseTypeOptions"
-                    :key="dict.dictValue"
-                    :label="dict.dictLabel"
-                    :value="dict.dictValue"
-                  />
-              </el-select>
-            </el-form-item>
-          </el-col> -->
-          <el-col :span="8">
-            <el-form-item label="关联达人" prop="talentId" >
-              <el-select v-model="form.talentId" remote filterable clearable reserve-keyword placeholder="输入手机号搜索" :remote-method="talentMethod" >
-                <el-option
-                  v-for="item in talentList"
-                  :key="item.talentId"
-                  :label="item.nickName +'#'+item.phone"
-                  :value="item.talentId">
-                  <span style="float: left">{{ item.talentId }}</span>
-                  <span style="margin-left: 30px ;">{{item.nickName}}</span>
-                  <span style="margin-left: 30px">{{ item.phone }}</span>
-                </el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="课堂简介" prop="description">
-              <el-input v-model="form.description" type="textarea" :rows="2" placeholder="请输入课堂简介" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-form-item label="课程封面" prop="imgUrl">
-          <ImageUpload v-model="form.imgUrl" type="image" :num="10" :width="150" :height="150" />
-        </el-form-item>
-        <el-form-item label="小封面" prop="imgUrl">
-          <ImageUpload v-model="form.secondImg" type="image" :num="10" :width="150" :height="150" />
-        </el-form-item>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="标签" prop="tags">
-              <el-select v-model="tags" multiple placeholder="请选择标签" filterable clearable size="small">
-                <el-option
-                  v-for="dict in tagsOptions"
+                  v-for="dict in subCategoryOptions"
                   :key="dict.dictValue"
                   :label="dict.dictLabel"
                   :value="dict.dictValue"
@@ -301,129 +241,15 @@
           </el-col>
         </el-row>
         <el-row>
-          <el-col :span="8">
-            <el-form-item label="排序" prop="sort">
-              <el-input-number v-model="form.sort"  :min="0"  label="排序"></el-input-number>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="播放量" prop="views">
-              <el-input-number v-model="form.views" :min="0"  label="浏览量"></el-input-number>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="点赞量" prop="likes">
-              <el-input-number v-model="form.likes" :min="0"   label="点赞量"></el-input-number>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-
-          <el-col :span="8">
-            <el-form-item label="收藏数" prop="favoriteNum">
-              <el-input-number v-model="form.favoriteNum" :min="0"   label="收藏数"></el-input-number>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="分享数" prop="shares">
-              <el-input-number v-model="form.shares" :min="0"   label="分享数"></el-input-number>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="热度值" prop="hotNum">
-              <el-input-number v-model="form.hotNum" :min="0"   label="热度值"></el-input-number>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="8">
-            <el-form-item label="状态" prop="isShow">
-              <el-radio-group v-model="form.isShow">
-                <el-radio :label="item.dictValue" v-for="item in specShowOptions" >{{item.dictLabel}}</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="是否推荐" prop="isTui">
-              <el-radio-group v-model="form.isTui">
-                <el-radio :label="item.dictValue" v-for="item in orOptions" >{{item.dictLabel}}</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="是否精选" prop="isBest">
-              <el-radio-group v-model="form.isBest">
-                <el-radio :label="item.dictValue" v-for="item in orOptions" >{{item.dictLabel}}</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="8">
-            <el-form-item label="是否自动播放" prop="isAutoPlay">
-              <el-radio-group v-model="form.isAutoPlay">
-                <el-radio :label="item.dictValue" v-for="item in orOptions" >{{item.dictLabel}}</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="是否允许快进" prop="isFast">
-              <el-radio-group v-model="form.isFast">
-                <el-radio :label="item.dictValue" v-for="item in orOptions" >{{item.dictLabel}}</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="是否积分兑换" prop="isIntegral">
-              <el-radio-group v-model="form.isIntegral">
-                <el-radio :label="item.dictValue" v-for="item in orOptions" >{{item.dictLabel}}</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="8">
-            <el-form-item label="是否逐级播放" prop="isNext">
-              <el-radio-group v-model="form.isNext">
-                <el-radio :label="item.dictValue" v-for="item in orOptions" >{{item.dictLabel}}</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="是否私域" prop="isPrivate">
-              <el-radio-group v-model="form.isPrivate">
-                <el-radio :label="item.dictValue" v-for="item in orOptions" >{{item.dictLabel}}</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="8">
-            <el-form-item label="课程原价" prop="price" >
-              <el-input v-model="form.price" placeholder="请输入课程原价" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="课程售价" prop="sellPrice" >
-              <el-input v-model="form.sellPrice" placeholder="请输入课程售价" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="单节所需积分" prop="integral" v-if="form.isIntegral==1">
-              <el-input v-model="form.integral" placeholder="请输入单节所需积分" />
+          <el-col :span="24">
+            <el-form-item label="课堂简介" prop="description">
+              <el-input v-model="form.description" type="textarea" :rows="2" placeholder="请输入课堂简介"/>
             </el-form-item>
           </el-col>
         </el-row>
-		<el-form-item label="关联公司" prop="tags">
-		  <el-select v-model="companyIds" multiple placeholder="请选择公司" filterable clearable style="width: 90%;">
-		    <el-option
-		      v-for="dict in companyOptions"
-		      :key="dict.dictValue"
-		      :label="dict.dictLabel"
-		      :value="dict.dictValue"
-		    />
-		  </el-select>
-		</el-form-item>
+        <el-form-item label="课程封面" prop="imgUrl">
+          <ImageUpload v-model="form.imgUrl" type="image" :num="10" :width="150" :height="150"/>
+        </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -431,53 +257,67 @@
       </div>
     </el-dialog>
     <el-drawer
-        :with-header="false"
-        size="75%"
-         :title="show.title" :visible.sync="show.open" append-to-body>
-     <userCourseCatalogDetails  ref="userCourseCatalogDetails" />
-   </el-drawer>
+      :with-header="false"
+      size="75%"
+      :title="show.title" :visible.sync="show.open" append-to-body>
+      <userCourseCatalogDetails ref="userCourseCatalogDetails"/>
+    </el-drawer>
   </div>
 </template>
 
 <script>
-import { listUserCourse, getUserCourse, delUserCourse, addUserCourse, updateUserCourse, exportUserCourse,updateIsShow,putOn,pullOff } from "@/api/course/userCourse";
+import {
+  listUserCourse,
+  getUserCourse,
+  delUserCourse,
+  addUserCourse,
+  updateUserCourse,
+  exportUserCourse,
+  updateIsShow,
+  putOn,
+  pullOff
+} from "@/api/course/userCourse";
+
+import {getSelectableRange} from "@/api/qw/sopTemp";
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
 import Editor from '@/components/Editor/wang';
 import ImageUpload from '@/components/ImageUpload/index';
-import { listBySearch} from "@/api/course/userTalent";
+import {listBySearch} from "@/api/course/userTalent";
 import userCourseCatalogDetails from '../../components/course/userCourseCatalogDetails.vue';
-import { getAllCourseCategoryList,getCatePidList ,getCateListByPid} from "@/api/course/userCourseCategory";
+import {getAllCourseCategoryList, getCatePidList, getCateListByPid} from "@/api/course/userCourseCategory";
 import {allList} from "@/api/company/company";
+
 export default {
   name: "UserCourse",
   components: {
     Treeselect,
-    Editor,ImageUpload,userCourseCatalogDetails
+    Editor, ImageUpload, userCourseCatalogDetails
   },
   data() {
     return {
-      talentParam:{
-        phone:null,
-        talentId:null
+      talentParam: {
+        phone: null,
+        talentId: null
       },
-      talentList:[],
-      show:{
-        title:"目录管理",
-        open:false
+      talentList: [],
+      startTimeRange: [],
+      show: {
+        title: "目录管理",
+        open: false
       },
-      activeName:"1",
-      projectOptions:[],
-      tagsOptions:[],
-      tags:[],
-	  companyIds:[],
-      courseTypeOptions:[],
+      activeName: "1",
+      projectOptions: [],
+      tagsOptions: [],
+      tags: [],
+      companyIds: [],
+      courseTypeOptions: [],
       orOptions: [],
       specShowOptions: [],
       specTypeOptions: [],
-      categoryOptions:[],
-      subCategoryOptions:[],
-      querySubCateOptions:[],
+      categoryOptions: [],
+      subCategoryOptions: [],
+      querySubCateOptions: [],
       // 遮罩层
       loading: true,
       // 导出遮罩层
@@ -494,7 +334,7 @@ export default {
       total: 0,
       // 课程表格数据
       userCourseList: [],
-	  companyOptions:[],
+      companyOptions: [],
       // 弹出层标题
       title: "",
       // 是否显示弹出层
@@ -504,7 +344,7 @@ export default {
         pageNum: 1,
         pageSize: 10,
         cateId: null,
-        subCateId:null,
+        subCateId: null,
         title: null,
         imgUrl: null,
         userId: null,
@@ -518,56 +358,57 @@ export default {
         description: null,
         hotRanking: null,
         integral: null,
-        price: null
+        price: null,
+        isPrivate: 1
       },
       // 表单参数
       form: {},
       // 表单校验
       rules: {
         courseName: [
-          { required: true, message: "课堂名称不能为空", trigger: "blur" }
+          {required: true, message: "课堂名称不能为空", trigger: "blur"}
         ],
         imgUrl: [
-          { required: true, message: "封面图片不能为空", trigger: "blur" }
+          {required: true, message: "封面图片不能为空", trigger: "blur"}
         ],
         isTui: [
-          { required: true, message: "是否推荐不能为空", trigger: "blur" }
+          {required: true, message: "是否推荐不能为空", trigger: "blur"}
         ],
         isBest: [
-          { required: true, message: "是否精选不能为空", trigger: "blur" }
+          {required: true, message: "是否精选不能为空", trigger: "blur"}
         ],
         isFast: [
-          { required: true, message: "是否允许快进不能为空", trigger: "blur" }
+          {required: true, message: "是否允许快进不能为空", trigger: "blur"}
         ],
         isAutoPlay: [
-          { required: true, message: "是否自动播放不能为空", trigger: "blur" }
+          {required: true, message: "是否自动播放不能为空", trigger: "blur"}
         ],
         sort: [
-          { required: true, message: "排序不能为空", trigger: "blur" }
+          {required: true, message: "排序不能为空", trigger: "blur"}
         ],
         views: [
-          { required: true, message: "播放量不能为空", trigger: "blur" }
+          {required: true, message: "播放量不能为空", trigger: "blur"}
         ],
         likes: [
-          { required: true, message: "点赞数不能为空", trigger: "blur" }
+          {required: true, message: "点赞数不能为空", trigger: "blur"}
         ],
         favoriteNum: [
-          { required: true, message: "收藏数不能为空", trigger: "blur" }
+          {required: true, message: "收藏数不能为空", trigger: "blur"}
         ],
         shares: [
-          { required: true, message: "分享数不能为空", trigger: "blur" }
+          {required: true, message: "分享数不能为空", trigger: "blur"}
         ],
         isIntegral: [
-          { required: true, message: "是否允许积分兑换不能为空", trigger: "blur" }
+          {required: true, message: "是否允许积分兑换不能为空", trigger: "blur"}
         ],
         isShow: [
-          { required: true, message: "上架状态不能为空", trigger: "blur" }
+          {required: true, message: "上架状态不能为空", trigger: "blur"}
         ],
         isPrivate: [
-          { required: true, message: "公私域不能为空", trigger: "blur" }
+          {required: true, message: "公私域不能为空", trigger: "blur"}
         ],
         integral: [
-          { required: true, message: "小节兑换积分不能为空", trigger: "blur" }
+          {required: true, message: "小节兑换积分不能为空", trigger: "blur"}
         ],
       }
     };
@@ -575,9 +416,13 @@ export default {
   created() {
     this.getList();
     getCatePidList().then(response => {
-        this.categoryOptions = response.data;
+      this.categoryOptions = response.data;
     });
 
+
+    getSelectableRange().then(e => {
+      this.startTimeRange = e.data;
+    })
     // this.getTreeselect();
     this.getDicts("sys_spec_show").then(response => {
       this.specShowOptions = response.data;
@@ -597,15 +442,15 @@ export default {
     this.getDicts("sys_company_or").then(response => {
       this.orOptions = response.data;
     });
-	allList().then(response => {
-	    this.companyOptions = response.rows;
-	});
+    allList().then(response => {
+      this.companyOptions = response.rows;
+    });
   },
   methods: {
-    selectTalent(){
+    selectTalent() {
 
     },
-    talentMethod(query){
+    talentMethod(query) {
       if (query !== '') {
         this.talentParam.phone = query;
         listBySearch(this.talentParam).then(response => {
@@ -613,44 +458,44 @@ export default {
         });
       }
     },
-    getSubCateList(pid){
-      this.form.subCateId=null;
-      if(pid == ''){
-        this.subCategoryOptions=[];
+    getSubCateList(pid) {
+      this.form.subCateId = null;
+      if (pid == '') {
+        this.subCategoryOptions = [];
         return
       }
       getCateListByPid(pid).then(response => {
         this.subCategoryOptions = response.data;
       });
     },
-    getQuerySubCateList(pid){
-      this.queryParams.subCateId=null;
-      if(pid == ''){
-        this.querySubCateOptions=[];
+    getQuerySubCateList(pid) {
+      this.queryParams.subCateId = null;
+      if (pid == '') {
+        this.querySubCateOptions = [];
         return
       }
-      this.queryParams.subCateId=null;
+      this.queryParams.subCateId = null;
       getCateListByPid(pid).then(response => {
         this.querySubCateOptions = response.data;
       });
     },
     handleShow(row) {
       var isShowValue = row.isShow === 0 ? 1 : 0;
-      var course = { courseId: row.courseId, isShow: isShowValue };
+      var course = {courseId: row.courseId, isShow: isShowValue};
       updateIsShow(course).then(response => {
         this.msgSuccess("修改成功");
         this.getList();
       });
     },
-    handleCatalog(row){
+    handleCatalog(row) {
       const courseId = row.courseId;
-      this.show.open=true;
-        setTimeout(() => {
-             this.$refs.userCourseCatalogDetails.getDetails(courseId,row.courseName,row.isPrivate);
-        }, 200);
+      this.show.open = true;
+      setTimeout(() => {
+        this.$refs.userCourseCatalogDetails.getDetails(courseId, row.courseName, row.isPrivate);
+      }, 200);
     },
     handleClick(tab, event) {
-      this.queryParams.isShow=tab.name;
+      this.queryParams.isShow = tab.name;
       this.getList();
     },
     /** 转换课堂分类数据结构 */
@@ -668,7 +513,7 @@ export default {
       getAllCourseCategoryList().then(response => {
         this.categoryOptions = [];
         const data = this.handleTree(response.data, "cateId", "pid");
-        this.categoryOptions=data;
+        this.categoryOptions = data;
       });
     },
     /** 查询课程列表 */
@@ -690,10 +535,10 @@ export default {
       this.form = {
         courseId: null,
         cateId: null,
-        subCateId:null,
+        subCateId: null,
         title: null,
         imgUrl: null,
-        secondImg:null,
+        secondImg: null,
         userId: null,
         sort: null,
         createTime: null,
@@ -701,26 +546,26 @@ export default {
         status: 0,
         isVip: null,
         isAutoPlay: "1",
-        isIntegral:"0",
+        isIntegral: "0",
         isShow: "1",
-        isFast:"1",
-        isTui:"1",
-        isBest:"1",
-        isNext:"1",
-        isPrivate:"1",
+        isFast: "1",
+        isTui: "1",
+        isBest: "1",
+        isNext: "1",
+        isPrivate: "1",
         views: 100000,
         duration: null,
         description: null,
         hotRanking: null,
         integral: null,
         price: null,
-        likes:100000,
-        shares:100000,
-        favoriteNum:100000,
-        hotNum:100000
+        likes: 100000,
+        shares: 100000,
+        favoriteNum: 100000,
+        hotNum: 100000,
       };
-      this.tags=[];
-      this.subCategoryOptions=[]
+      this.tags = [];
+      this.subCategoryOptions = []
       this.resetForm("form");
     },
     /** 搜索按钮操作 */
@@ -731,38 +576,40 @@ export default {
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm("queryForm");
-      this.queryParams.isShow=this.activeName
+      this.queryParams.isShow = this.activeName
       this.handleQuery();
     },
     // 多选框选中数据
     handleSelectionChange(selection) {
       this.ids = selection.map(item => item.courseId)
-      this.single = selection.length!==1
+      this.single = selection.length !== 1
       this.multiple = !selection.length
     },
     /** 新增按钮操作 */
     handleAdd() {
       this.reset();
-      this.talentList=[];
+      this.talentList = [];
       this.open = true;
       this.title = "添加课程";
     },
     /** 修改按钮操作 */
     handleUpdate(row) {
       this.reset();
-      this.talentList=[];
+      this.talentList = [];
       const courseId = row.courseId || this.ids
       getUserCourse(courseId).then(response => {
         this.form = response.data;
         // this.form.cateId = response.data.cateId.toString();
-        getCateListByPid(this.form.cateId).then(response => {
-          this.subCategoryOptions = response.data;
-        });
+        if (this.form.cateId) {
+          getCateListByPid(this.form.cateId).then(response => {
+            this.subCategoryOptions = response.data;
+          });
+        }
         // this.form.courseType = response.data.courseType.toString();
-        if(response.data.project!=null){
+        if (response.data.project != null) {
           this.form.project = response.data.project.toString();
         }
-        if(response.data.tags!=null){
+        if (response.data.tags != null) {
           this.tags = response.data.tags.split(",")
         }
         this.form.isAutoPlay = response.data.isAutoPlay.toString();
@@ -774,11 +621,11 @@ export default {
         this.form.isNext = response.data.isNext.toString();
         this.form.isPrivate = response.data.isPrivate.toString();
         this.talentParam.talentId = response.data.talentId;
-		if(this.form.companyIds!=null){
-			this.companyIds=((this.form.companyIds).split(",").map(Number))
-		}else{
-			this.companyIds=[]
-		}
+        if (this.form.companyIds != null) {
+          this.companyIds = ((this.form.companyIds).split(",").map(Number))
+        } else {
+          this.companyIds = []
+        }
 
         listBySearch(this.talentParam).then(response => {
           this.talentList = response.data;
@@ -791,13 +638,9 @@ export default {
     submitForm() {
       this.$refs["form"].validate(valid => {
         if (valid) {
-          if(this.tags.length>0){
-              this.form.tags=this.tags.toString();
-            }
-            else{
-              this.form.tags=null
-            }
-			this.form.companyIds=this.companyIds.toString()
+
+          // 私域课程
+          this.form.isPrivate = 1
           if (this.form.courseId != null) {
             updateUserCourse(this.form).then(response => {
               this.msgSuccess("修改成功");
@@ -818,62 +661,66 @@ export default {
     handleDelete(row) {
       const courseIds = row.courseId || this.ids;
       this.$confirm('是否确认删除课程编号为"' + courseIds + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return delUserCourse(courseIds);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("删除成功");
-        }).catch(() => {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delUserCourse(courseIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {
+      });
     },
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有课程数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(() => {
-          this.exportLoading = true;
-          return exportUserCourse(queryParams);
-        }).then(response => {
-          this.download(response.msg);
-          this.exportLoading = false;
-        }).catch(() => {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportUserCourse(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+      });
     },
     putOn() {
-      const courseIds =this.ids;
-      if(courseIds==null||courseIds==""){
-         return this.$message("未选择课程");
+      const courseIds = this.ids;
+      if (courseIds == null || courseIds == "") {
+        return this.$message("未选择课程");
       }
       this.$confirm('是否确认批量上架课程?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return putOn(courseIds);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("上架成功");
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return putOn(courseIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("上架成功");
+      }).catch(function () {
+      });
     },
     pullOff() {
-      const courseIds =this.ids;
-      if(courseIds==null||courseIds==""){
-         return this.$message("未选择课程");
+      const courseIds = this.ids;
+      if (courseIds == null || courseIds == "") {
+        return this.$message("未选择课程");
       }
       this.$confirm('是否确认批量下架课程?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return pullOff(courseIds);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("下架成功");
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return pullOff(courseIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("下架成功");
+      }).catch(function () {
+      });
     }
   }
 };

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

@@ -0,0 +1,929 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="课堂分类" prop="cateId">
+        <el-select v-model="queryParams.cateId" placeholder="请选择" clearable size="small"
+                   @change="getQuerySubCateList(queryParams.cateId)">
+          <el-option
+            v-for="dict in categoryOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课堂子分类" prop="subCateId">
+        <el-select v-model="queryParams.subCateId" placeholder="请选择" clearable size="small">
+          <el-option
+            v-for="dict in querySubCateOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课堂名称" prop="courseName">
+        <el-input
+          v-model="queryParams.courseName"
+          placeholder="请输入课堂名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="课堂类型" prop="isPrivate" style="display: none">
+        <el-select v-model="queryParams.isPrivate" placeholder="请选择" clearable size="small">
+          <el-option
+            v-for="dict in courseTypeOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </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="['course:userCourse: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="['course:userCourse: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="['course:userCourse: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="['course:userCourse:export']"
+        >导出
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          v-if="queryParams.isShow==0"
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="multiple"
+          @click="putOn"
+          v-hasPermi="['course:userCourse:putOn']"
+        >上架
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          v-if="queryParams.isShow==1"
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="multiple"
+          @click="pullOff"
+          v-hasPermi="['course:userCourse:pullOff']"
+        >下架
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-tabs type="card" v-model="queryParams.isShow" @tab-click="handleClick">
+      <el-tab-pane label="已上架" name="1"></el-tab-pane>
+      <el-tab-pane label="待上架" name="0"></el-tab-pane>
+    </el-tabs>
+    <el-table height="600" border v-loading="loading" :data="userCourseList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center"/>
+      <el-table-column label="课程ID" align="center" prop="courseId"/>
+      <el-table-column label="所属项目" align="center" prop="projectName"/>
+      <el-table-column label="封面图片" align="center" prop="imgUrl" width="120">
+        <template slot-scope="scope">
+          <el-popover
+            placement="right"
+            title=""
+            trigger="hover"
+          >
+            <img slot="reference" :src="scope.row.imgUrl" width="100">
+            <img :src="scope.row.imgUrl" style="max-width: 300px;">
+          </el-popover>
+        </template>
+      </el-table-column>
+      <el-table-column label="小封面" align="center" prop="secondImg" width="100">
+        <template slot-scope="scope">
+          <el-popover
+            placement="right"
+            title=""
+            trigger="hover"
+          >
+            <img slot="reference" :src="scope.row.secondImg" width="50">
+            <img :src="scope.row.secondImg" style="max-width: 100px;">
+          </el-popover>
+        </template>
+      </el-table-column>
+      <el-table-column label="课堂名称" align="center" show-overflow-tooltip prop="courseName"/>
+      <el-table-column label="排序" align="center" prop="sort"/>
+      <el-table-column label="分类名称" align="center" prop="cateName"/>
+      <el-table-column label="子分类名称" align="center" prop="subCateName"/>
+      <el-table-column label="原价" align="center" prop="price"/>
+      <el-table-column label="售价" align="center" prop="sellPrice"/>
+      <el-table-column label="单节积分" align="center" prop="integral"/>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleCatalog(scope.row)"
+          >目录管理
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            :icon="scope.row.isShow === 1 ? 'el-icon-close' : 'el-icon-open'"
+            @click="handleShow(scope.row)"
+            v-hasPermi="['course:userCourse:editShow']"
+          >
+            {{ scope.row.isShow === 1 ? '下架' : '上架' }}
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['course:userCourse:edit']"
+          >修改
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['course:userCourse: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="1200px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="110px">
+        <el-row>
+          <el-form-item label="所属项目" prop="project">
+            <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-row>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="课堂名称" prop="courseName">
+              <el-input v-model="form.courseName" placeholder="请输入课堂名称"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="课堂分类" prop="cateId">
+              <el-select v-model="form.cateId" placeholder="请选择" clearable size="small"
+                         @change="getSubCateList(form.cateId)">
+                <el-option
+                  v-for="dict in categoryOptions"
+                  :key="dict.dictValue"
+                  :label="dict.dictLabel"
+                  :value="dict.dictValue"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="课堂子分类" prop="subCateId">
+              <el-select v-model="form.subCateId" placeholder="请选择" clearable size="small">
+                <el-option
+                  v-for="dict in subCategoryOptions"
+                  :key="dict.dictValue"
+                  :label="dict.dictLabel"
+                  :value="dict.dictValue"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <!-- <el-col :span="8">
+            <el-form-item label="课堂类型" prop="courseType">
+              <el-select v-model="form.courseType" placeholder="请选择" clearable size="small">
+                <el-option
+                    v-for="dict in courseTypeOptions"
+                    :key="dict.dictValue"
+                    :label="dict.dictLabel"
+                    :value="dict.dictValue"
+                  />
+              </el-select>
+            </el-form-item>
+          </el-col> -->
+          <el-col :span="8">
+            <el-form-item label="关联达人" prop="talentId">
+              <el-select v-model="form.talentId" remote filterable clearable reserve-keyword
+                         placeholder="输入手机号搜索" :remote-method="talentMethod">
+                <el-option
+                  v-for="item in talentList"
+                  :key="item.talentId"
+                  :label="item.nickName +'#'+item.phone"
+                  :value="item.talentId">
+                  <span style="float: left">{{ item.talentId }}</span>
+                  <span style="margin-left: 30px ;">{{ item.nickName }}</span>
+                  <span style="margin-left: 30px">{{ item.phone }}</span>
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="课堂简介" prop="description">
+              <el-input v-model="form.description" type="textarea" :rows="2" placeholder="请输入课堂简介"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="课程封面" prop="imgUrl">
+          <ImageUpload v-model="form.imgUrl" type="image" :num="10" :width="150" :height="150"/>
+        </el-form-item>
+        <el-form-item label="小封面" prop="imgUrl">
+          <ImageUpload v-model="form.secondImg" type="image" :num="10" :width="150" :height="150"/>
+        </el-form-item>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="标签" prop="tags">
+              <el-select v-model="tags" multiple placeholder="请选择标签" filterable clearable size="small">
+                <el-option
+                  v-for="dict in tagsOptions"
+                  :key="dict.dictValue"
+                  :label="dict.dictLabel"
+                  :value="dict.dictValue"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="发课时间" prop="sendTime">
+              <el-time-picker
+                v-model="form.sendTime"
+                value-format="HH:mm"
+                format="HH:mm"
+                style="width: 100px"
+                :picker-options="{ selectableRange: startTimeRange }"
+              >
+              </el-time-picker>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="排序" prop="sort">
+              <el-input-number v-model="form.sort" :min="0" label="排序"></el-input-number>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="播放量" prop="views">
+              <el-input-number v-model="form.views" :min="0" label="浏览量"></el-input-number>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="点赞量" prop="likes">
+              <el-input-number v-model="form.likes" :min="0" label="点赞量"></el-input-number>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+
+          <el-col :span="8">
+            <el-form-item label="收藏数" prop="favoriteNum">
+              <el-input-number v-model="form.favoriteNum" :min="0" label="收藏数"></el-input-number>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="分享数" prop="shares">
+              <el-input-number v-model="form.shares" :min="0" label="分享数"></el-input-number>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="热度值" prop="hotNum">
+              <el-input-number v-model="form.hotNum" :min="0" label="热度值"></el-input-number>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="状态" prop="isShow">
+              <el-radio-group v-model="form.isShow">
+                <el-radio :label="item.dictValue" v-for="item in specShowOptions">{{ item.dictLabel }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="是否推荐" prop="isTui">
+              <el-radio-group v-model="form.isTui">
+                <el-radio :label="item.dictValue" v-for="item in orOptions">{{ item.dictLabel }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="是否精选" prop="isBest">
+              <el-radio-group v-model="form.isBest">
+                <el-radio :label="item.dictValue" v-for="item in orOptions">{{ item.dictLabel }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="是否自动播放" prop="isAutoPlay">
+              <el-radio-group v-model="form.isAutoPlay">
+                <el-radio :label="item.dictValue" v-for="item in orOptions">{{ item.dictLabel }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="是否允许快进" prop="isFast">
+              <el-radio-group v-model="form.isFast">
+                <el-radio :label="item.dictValue" v-for="item in orOptions">{{ item.dictLabel }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="是否积分兑换" prop="isIntegral">
+              <el-radio-group v-model="form.isIntegral">
+                <el-radio :label="item.dictValue" v-for="item in orOptions">{{ item.dictLabel }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="是否逐级播放" prop="isNext">
+              <el-radio-group v-model="form.isNext">
+                <el-radio :label="item.dictValue" v-for="item in orOptions">{{ item.dictLabel }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8" style="display: none">
+            <el-form-item label="是否私域" prop="isPrivate">
+              <el-radio-group v-model="form.isPrivate">
+                <el-radio :label="item.dictValue" v-for="item in orOptions">{{ item.dictLabel }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="课程原价" prop="price">
+              <el-input v-model="form.price" placeholder="请输入课程原价"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="课程售价" prop="sellPrice">
+              <el-input v-model="form.sellPrice" placeholder="请输入课程售价"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="单节所需积分" prop="integral" v-if="form.isIntegral==1">
+              <el-input v-model="form.integral" placeholder="请输入单节所需积分"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="关联公司" prop="tags">
+          <el-select v-model="companyIds" multiple placeholder="请选择公司" filterable clearable style="width: 90%;">
+            <el-option
+              v-for="dict in companyOptions"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+    <el-drawer
+      :with-header="false"
+      size="75%"
+      :title="show.title" :visible.sync="show.open" append-to-body>
+      <userCourseCatalogDetails ref="userCourseCatalogDetails"/>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import {
+  listUserCourse,
+  getUserCourse,
+  delUserCourse,
+  addUserCourse,
+  updateUserCourse,
+  exportUserCourse,
+  updateIsShow,
+  putOn,
+  pullOff
+} from "@/api/course/userCourse";
+
+import {getSelectableRange} from "@/api/qw/sopTemp";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import Editor from '@/components/Editor/wang';
+import ImageUpload from '@/components/ImageUpload/index';
+import {listBySearch} from "@/api/course/userTalent";
+import userCourseCatalogDetails from '../../components/course/userCourseCatalogDetails.vue';
+import {getAllCourseCategoryList, getCatePidList, getCateListByPid} from "@/api/course/userCourseCategory";
+import {allList} from "@/api/company/company";
+
+export default {
+  name: "UserCoursePublic",
+  components: {
+    Treeselect,
+    Editor, ImageUpload, userCourseCatalogDetails
+  },
+  data() {
+    return {
+      talentParam: {
+        phone: null,
+        talentId: null
+      },
+      talentList: [],
+      startTimeRange: [],
+      show: {
+        title: "目录管理",
+        open: false
+      },
+      activeName: "1",
+      projectOptions: [],
+      tagsOptions: [],
+      tags: [],
+      companyIds: [],
+      courseTypeOptions: [],
+      orOptions: [],
+      specShowOptions: [],
+      specTypeOptions: [],
+      categoryOptions: [],
+      subCategoryOptions: [],
+      querySubCateOptions: [],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 课程表格数据
+      userCourseList: [],
+      companyOptions: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        cateId: null,
+        subCateId: null,
+        title: null,
+        imgUrl: null,
+        userId: null,
+        sort: null,
+        status: null,
+        isVip: null,
+        isHot: null,
+        isShow: "1",
+        views: null,
+        duration: null,
+        description: null,
+        hotRanking: null,
+        integral: null,
+        price: null,
+        isPrivate: 0
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        courseName: [
+          {required: true, message: "课堂名称不能为空", trigger: "blur"}
+        ],
+        imgUrl: [
+          {required: true, message: "封面图片不能为空", trigger: "blur"}
+        ],
+        isTui: [
+          {required: true, message: "是否推荐不能为空", trigger: "blur"}
+        ],
+        isBest: [
+          {required: true, message: "是否精选不能为空", trigger: "blur"}
+        ],
+        isFast: [
+          {required: true, message: "是否允许快进不能为空", trigger: "blur"}
+        ],
+        isAutoPlay: [
+          {required: true, message: "是否自动播放不能为空", trigger: "blur"}
+        ],
+        sort: [
+          {required: true, message: "排序不能为空", trigger: "blur"}
+        ],
+        views: [
+          {required: true, message: "播放量不能为空", trigger: "blur"}
+        ],
+        likes: [
+          {required: true, message: "点赞数不能为空", trigger: "blur"}
+        ],
+        favoriteNum: [
+          {required: true, message: "收藏数不能为空", trigger: "blur"}
+        ],
+        shares: [
+          {required: true, message: "分享数不能为空", trigger: "blur"}
+        ],
+        isIntegral: [
+          {required: true, message: "是否允许积分兑换不能为空", trigger: "blur"}
+        ],
+        isShow: [
+          {required: true, message: "上架状态不能为空", trigger: "blur"}
+        ],
+        isPrivate: [
+          {required: true, message: "公私域不能为空", trigger: "blur"}
+        ],
+        integral: [
+          {required: true, message: "小节兑换积分不能为空", trigger: "blur"}
+        ],
+      }
+    };
+  },
+  created() {
+    this.getList();
+    getCatePidList().then(response => {
+      this.categoryOptions = response.data;
+    });
+
+
+    getSelectableRange().then(e => {
+      this.startTimeRange = e.data;
+    })
+    // this.getTreeselect();
+    this.getDicts("sys_spec_show").then(response => {
+      this.specShowOptions = response.data;
+    });
+    this.getDicts("sys_spec_type").then(response => {
+      this.specTypeOptions = response.data;
+    });
+    this.getDicts("sys_course_type").then(response => {
+      this.courseTypeOptions = response.data;
+    });
+    this.getDicts("sys_course_project").then(response => {
+      this.projectOptions = response.data;
+    });
+    this.getDicts("sys_course_tags").then(response => {
+      this.tagsOptions = response.data;
+    });
+    this.getDicts("sys_company_or").then(response => {
+      this.orOptions = response.data;
+    });
+    allList().then(response => {
+      this.companyOptions = response.rows;
+    });
+  },
+  methods: {
+    selectTalent() {
+
+    },
+    talentMethod(query) {
+      if (query !== '') {
+        this.talentParam.phone = query;
+        listBySearch(this.talentParam).then(response => {
+          this.talentList = response.data;
+        });
+      }
+    },
+    getSubCateList(pid) {
+      this.form.subCateId = null;
+      if (pid == '') {
+        this.subCategoryOptions = [];
+        return
+      }
+      getCateListByPid(pid).then(response => {
+        this.subCategoryOptions = response.data;
+      });
+    },
+    getQuerySubCateList(pid) {
+      this.queryParams.subCateId = null;
+      if (pid == '') {
+        this.querySubCateOptions = [];
+        return
+      }
+      this.queryParams.subCateId = null;
+      getCateListByPid(pid).then(response => {
+        this.querySubCateOptions = response.data;
+      });
+    },
+    handleShow(row) {
+      var isShowValue = row.isShow === 0 ? 1 : 0;
+      var course = {courseId: row.courseId, isShow: isShowValue};
+      updateIsShow(course).then(response => {
+        this.msgSuccess("修改成功");
+        this.getList();
+      });
+    },
+    handleCatalog(row) {
+      const courseId = row.courseId;
+      this.show.open = true;
+      setTimeout(() => {
+        this.$refs.userCourseCatalogDetails.getDetails(courseId, row.courseName, row.isPrivate);
+      }, 200);
+    },
+    handleClick(tab, event) {
+      this.queryParams.isShow = tab.name;
+      this.getList();
+    },
+    /** 转换课堂分类数据结构 */
+    normalizer(node) {
+      if (node.children && !node.children.length) {
+        delete node.children;
+      }
+      return {
+        id: node.cateId,
+        label: node.cateName,
+        children: node.children
+      };
+    },
+    getTreeselect() {
+      getAllCourseCategoryList().then(response => {
+        this.categoryOptions = [];
+        const data = this.handleTree(response.data, "cateId", "pid");
+        this.categoryOptions = data;
+      });
+    },
+    /** 查询课程列表 */
+    getList() {
+      this.loading = true;
+      listUserCourse(this.queryParams).then(response => {
+        this.userCourseList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        courseId: null,
+        cateId: null,
+        subCateId: null,
+        title: null,
+        imgUrl: null,
+        secondImg: null,
+        userId: null,
+        sort: null,
+        createTime: null,
+        updateTime: null,
+        status: 0,
+        isVip: null,
+        isAutoPlay: "1",
+        isIntegral: "0",
+        isShow: "1",
+        isFast: "1",
+        isTui: "1",
+        isBest: "1",
+        isNext: "1",
+        isPrivate: "0",
+        views: 100000,
+        duration: null,
+        description: null,
+        hotRanking: null,
+        integral: null,
+        price: null,
+        likes: 100000,
+        shares: 100000,
+        favoriteNum: 100000,
+        hotNum: 100000,
+      };
+      this.tags = [];
+      this.subCategoryOptions = []
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.queryParams.isShow = this.activeName
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.courseId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.talentList = [];
+      this.open = true;
+      this.title = "添加课程";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      this.talentList = [];
+      const courseId = row.courseId || this.ids
+      getUserCourse(courseId).then(response => {
+        this.form = response.data;
+        // this.form.cateId = response.data.cateId.toString();
+        getCateListByPid(this.form.cateId).then(response => {
+          this.subCategoryOptions = response.data;
+        });
+        // this.form.courseType = response.data.courseType.toString();
+        if (response.data.project != null) {
+          this.form.project = response.data.project.toString();
+        }
+        if (response.data.tags != null) {
+          this.tags = response.data.tags.split(",")
+        }
+        this.form.isAutoPlay = response.data.isAutoPlay.toString();
+        this.form.isShow = response.data.isShow.toString();
+        this.form.isBest = response.data.isBest.toString();
+        this.form.isFast = response.data.isFast.toString();
+        this.form.isIntegral = response.data.isIntegral.toString();
+        this.form.isTui = response.data.isTui.toString();
+        this.form.isNext = response.data.isNext.toString();
+        this.form.isPrivate = response.data.isPrivate.toString();
+        this.talentParam.talentId = response.data.talentId;
+        if (this.form.companyIds != null) {
+          this.companyIds = ((this.form.companyIds).split(",").map(Number))
+        } else {
+          this.companyIds = []
+        }
+
+        listBySearch(this.talentParam).then(response => {
+          this.talentList = response.data;
+        });
+        this.open = true;
+        this.title = "修改课程";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.tags.length > 0) {
+            this.form.tags = this.tags.toString();
+          } else {
+            this.form.tags = null
+          }
+          this.form.companyIds = this.companyIds.toString()
+          this.form.isPrivate = 0
+          if (this.form.courseId != null) {
+            updateUserCourse(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addUserCourse(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const courseIds = row.courseId || this.ids;
+      this.$confirm('是否确认删除课程编号为"' + courseIds + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delUserCourse(courseIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有课程数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportUserCourse(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+      });
+    },
+    putOn() {
+      const courseIds = this.ids;
+      if (courseIds == null || courseIds == "") {
+        return this.$message("未选择课程");
+      }
+      this.$confirm('是否确认批量上架课程?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return putOn(courseIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("上架成功");
+      }).catch(function () {
+      });
+    },
+    pullOff() {
+      const courseIds = this.ids;
+      if (courseIds == null || courseIds == "") {
+        return this.$message("未选择课程");
+      }
+      this.$confirm('是否确认批量下架课程?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return pullOff(courseIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("下架成功");
+      }).catch(function () {
+      });
+    }
+  }
+};
+</script>

+ 126 - 0
src/views/course/userCoursePeriod/batchRedPacket.vue

@@ -0,0 +1,126 @@
+<template>
+  <el-dialog
+    title="批量设置红包"
+    :visible.sync="visible"
+    width="800px"
+    append-to-body
+  >
+    <el-table
+      v-loading="loading"
+      :data="tableData"
+      border
+      style="width: 100%"
+    >
+      <el-table-column
+        type="index"
+        label="序号"
+        width="80"
+        align="center"
+      />
+      <el-table-column
+        prop="periodName"
+        label="营期"
+        align="center"
+      />
+      <el-table-column
+        label="金额"
+        align="center"
+        width="200"
+      >
+        <template slot-scope="scope">
+          <el-input-number
+            v-model="scope.row.amount"
+            :min="0.01"
+            :precision="2"
+            :step="0.01"
+            size="small"
+            style="width: 150px"
+          />
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="handleClose">取 消</el-button>
+      <el-button type="primary" @click="handleSave">保 存</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { batchSaveRedPacketByPeriod } from "@/api/course/userCoursePeriod";
+
+export default {
+  name: 'BatchRedPacket',
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    selectedData: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      loading: false,
+      tableData: []
+    }
+  },
+  watch: {
+    visible(val) {
+      if (val) {
+        this.initTableData()
+      }
+    }
+  },
+  methods: {
+    // 初始化表格数据
+    initTableData() {
+      this.tableData = this.selectedData.map(item => ({
+        ...item,
+        amount: 0.01
+      }))
+    },
+    // 保存
+    handleSave() {
+      this.$confirm(`是否确定?确定后营期下的所有公司红包金额都将设置成对应值`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.loading = true;
+        const saveData = this.tableData.map(item => ({
+          periodId: item.periodId,
+          redPacketMoney: item.amount
+        }));
+
+        batchSaveRedPacketByPeriod(saveData).then(response => {
+          if (response.code === 200) {
+            this.$message.success('设置成功');
+            this.$emit('success');
+            this.handleClose();
+          } else {
+            this.$message.error(response.msg || '批量设置失败');
+          }
+        }).catch(error => {
+          this.$message.error('批量设置失败:' + error.message);
+        }).finally(() => {
+          this.loading = false;
+        });
+      })
+    },
+    // 关闭
+    handleClose() {
+      this.$emit('update:visible', false)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.el-input-number {
+  width: 100%;
+}
+</style>

+ 575 - 124
src/views/course/userCoursePeriod/index.vue

@@ -34,7 +34,7 @@
         </div>
 
         <!-- 训练营列表 -->
-        <div class="camp-list" ref="campList" @scroll="handleScroll">
+        <div class="camp-list" ref="campList" @scroll="handleScroll" v-loading="leftLoading">
           <div
             v-for="(item, index) in campList"
             :key="index"
@@ -84,8 +84,8 @@
       <!-- 右侧区域 -->
       <el-main>
         <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
-          <el-form-item label="营期名称" prop="periodName" >
-            <el-input style="width: 150px"
+          <el-form-item label="营期名称" prop="periodName">
+            <el-input
               v-model="queryParams.periodName"
               placeholder="请输入营期名称"
               clearable
@@ -93,8 +93,8 @@
               @keyup.enter.native="handleQuery"
             />
           </el-form-item>
-          <el-form-item label="公司" prop="companyIdList" label-width="68px">
-            <el-select v-model="queryParams.companyIdList" placeholder="请选择公司" clearable size="small" multiple style="width: 150px">
+          <el-form-item label="公司" prop="companyIdList">
+            <el-select v-model="queryParams.companyIdList" placeholder="请选择公司" clearable size="small" multiple>
               <el-option
                 v-for="item in companyOptions"
                 :key="item.companyId"
@@ -103,20 +103,20 @@
               />
             </el-select>
           </el-form-item>
-          <el-form-item label="开始日期" prop="periodStartingTime" label-width="80px">
-            <el-date-picker clearable size="small" style="width: 120px"
+          <el-form-item label="开营日期开始" prop="periodStartingTime" label-width="120px">
+            <el-date-picker clearable size="small" style="width: 200px"
               v-model="queryParams.periodStartingTime"
               type="date"
               value-format="yyyy-MM-dd"
-              placeholder="开始时间">
+              placeholder="请选择开营日期开始时间">
             </el-date-picker>
           </el-form-item>
-          <el-form-item label="结束日期" prop="periodEndTime" label-width="80px">
-            <el-date-picker clearable size="small" style="width: 120px"
+          <el-form-item label="开营日期结束" prop="periodEndTime" label-width="120px">
+            <el-date-picker clearable size="small" style="width: 300px"
               v-model="queryParams.periodEndTime"
               type="date"
               value-format="yyyy-MM-dd"
-              placeholder="结束时间">
+              placeholder="请选择开营日期结束时间">
             </el-date-picker>
           </el-form-item>
           <el-form-item>
@@ -129,6 +129,7 @@
           <el-col :span="1.5">
             <el-button
               type="primary"
+              plain
               icon="el-icon-plus"
               size="mini"
               @click="handleAdd"
@@ -138,19 +139,32 @@
           <el-col :span="1.5">
             <el-button
               type="warning"
+              plain
               icon="el-icon-download"
               size="mini"
               @click="handleExport"
               v-hasPermi="['course:period:export']"
             >导出</el-button>
           </el-col>
+          <el-col :span="1.5">
+            <el-button
+              type="primary"
+              plain
+              icon="el-icon-edit"
+              size="mini"
+              @click="handleBatchSetRedPacket"
+              v-hasPermi="['course:period:export']"
+              :disabled="batchSetRedPacketDisabled"
+            >批量设置红包</el-button>
+          </el-col>
           <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
         </el-row>
 
-        <el-table v-loading="loading" :data="periodList">
-          <el-table-column label="所属训练营" align="center" prop="trainingCampName" />
+        <el-table v-loading="loading" :data="periodList" @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="55" align="center" />
           <el-table-column label="营期名称" align="center" prop="periodName" />
           <el-table-column label="公司名称" align="center" prop="companyName" />
+          <el-table-column label="营期状态" align="center" prop="periodStatus" width="100" :formatter="periodStatusFormatter" />
           <el-table-column label="开营开始时间" align="center" prop="periodStartingTime" width="180" />
           <el-table-column label="开营结束时间" align="center" prop="periodEndTime" width="180" />
           <el-table-column label="创建时间" align="center" prop="createTime" width="180" />
@@ -163,13 +177,31 @@
                 @click="handleUpdate(scope.row)"
                 v-hasPermi="['course:period:edit']"
               >修改</el-button>
+    <!--              <el-button-->
+    <!--                size="mini"-->
+    <!--                type="text"-->
+    <!--                icon="el-icon-edit"-->
+    <!--                @click="handleCourse(scope.row)"-->
+    <!--                v-hasPermi="['course:period:edit']"-->
+    <!--              >课程管理</el-button>-->
+    <!--              <el-button-->
+    <!--                size="mini"-->
+    <!--                type="text"-->
+    <!--                icon="el-icon-money"-->
+    <!--                @click="setRedPacket(scope.row)"-->
+    <!--              >设置红包</el-button>-->
               <el-button
                 size="mini"
                 type="text"
-                icon="el-icon-edit"
-                @click="handleCourse(scope.row)"
-                v-hasPermi="['course:period:edit']"
-              >课程管理</el-button>
+                icon="el-icon-setting"
+                @click="handlePeriodSettings(scope.row)"
+              >营期相关设置</el-button>
+              <el-button
+                size="mini"
+                type="text"
+                icon="el-icon-circle-close"
+                @click="handleClosePeriod(scope.row)"
+              >结束营期</el-button>
               <el-button
                 size="mini"
                 type="text"
@@ -179,25 +211,25 @@
               >删除</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-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
       </el-main>
     </el-container>
 
     <!-- 添加或修改会员营期对话框-->
-    <el-dialog :title="title" :visible.sync="open" width="700px" append-to-body>
+    <el-drawer :title="title" :visible.sync="open" width="700px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="营期名称" prop="periodName">
           <el-input v-model="form.periodName" placeholder="请输入营期名称" />
         </el-form-item>
-        <el-form-item label="公司id" prop="companyId">
+         <el-form-item label="公司" prop="companyId">
           <el-select v-model="form.companyId" placeholder="请选择公司" multiple>
             <el-option
               v-for="item in companyOptions"
@@ -227,6 +259,7 @@
         </el-form-item>
         <el-form-item label="开营日期" prop="periodStartingTime">
           <el-date-picker
+            :disabled = "isDisabledDateRange"
             :style="{display: form.periodType == 1 ? '' : 'none !important'}"
             v-model="form.dateRange"
             @change="timeChange(1)"
@@ -246,30 +279,33 @@
           </el-date-picker>
         </el-form-item>
 
-<!--        <div v-if="form.periodType == 1">-->
-<!--          <el-form-item label="开课时间" prop="periodType">-->
-<!--            <el-radio-group v-model="form.periodType">-->
-<!--              <el-radio :label="1" >多课程</el-radio>-->
-<!--              <el-radio :label="2" >单课程</el-radio>-->
-<!--            </el-radio-group>-->
-<!--          </el-form-item>-->
-<!--          <el-form-item :label="'第' + item.lesson + '节'" prop="periodStartingTime" v-for="item in form.days">-->
-<!--            <el-date-picker-->
-<!--              v-model="item.dateRange"-->
-<!--              type="datetimerange"-->
-<!--              range-separator="至"-->
-<!--              start-placeholder="开始日期"-->
-<!--              end-placeholder="结束日期"-->
-<!--              value-format="yyyy-MM-dd HH:mm:ss">-->
-<!--            </el-date-picker>-->
-<!--          </el-form-item>-->
-<!--        </div>-->
+<!--        <el-form-item label="看课时间" prop="timeRange">-->
+<!--          <el-time-picker-->
+<!--            is-range-->
+<!--            v-model="form.timeRange"-->
+<!--            @input="$forceUpdate()"-->
+<!--            range-separator="至"-->
+<!--            start-placeholder="开始时间"-->
+<!--            value-format="HH:mm:ss"-->
+<!--            end-placeholder="结束时间"-->
+<!--            placeholder="选择时间范围">-->
+<!--          </el-time-picker>-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="领取红包时间" prop="lastJoinTime">-->
+<!--          <el-time-picker-->
+<!--            v-model="form.lastJoinTime"-->
+<!--            :selectableRange="form.lastJoinTime"-->
+<!--            value-format="HH:mm:ss"-->
+<!--            placeholder="选择时间范围">-->
+<!--          </el-time-picker>-->
+<!--          <p style="color: red;margin: 0;font-size: 12px">超过领取红包时间,只允许看课,不允许领取红包</p>-->
+<!--        </el-form-item>-->
       </el-form>
-      <div slot="footer" class="dialog-footer">
+      <div class="drawer-footer">
         <el-button type="primary" @click="submitForm">确 定</el-button>
         <el-button @click="cancel">取 消</el-button>
       </div>
-    </el-dialog>
+    </el-drawer>
 
     <!-- 添加训练营对话框 -->
     <el-dialog :title="campForm.trainingCampId ? '修改训练营' : '新建训练营'" :visible.sync="campDialogVisible" width="500px" append-to-body>
@@ -284,70 +320,37 @@
       </div>
     </el-dialog>
 
-    <!-- 添加或修改会员营期对话框-->
-    <el-dialog title="课程管理" :visible.sync="course.open" width="90%" top="10px" append-to-body style="padding-bottom: 10px">
-      <el-row :gutter="10" class="mb8">
-        <el-col :span="1.5">
-          <el-button
-            v-if="(getDiff(course.row.periodStartingTime, course.row.periodEndTime) - course.total) > 0"
-            type="primary"
-            icon="el-icon-plus"
-            size="mini"
-            @click="handleAddCourse"
-            v-hasPermi="['course:period:add']"
-          >添加课程</el-button>
-        </el-col>
-      </el-row>
-      <el-table v-loading="course.loading" :data="course.list">
-        <el-table-column label="课程" align="center" prop="courseName" />
-        <el-table-column label="小节" align="center" prop="videoName" />
-        <el-table-column label="营期时间" align="center" prop="dayDate" />
-        <el-table-column v-if="course.row.redPacketGrantMethod == 2" label="红包金额" align="center" prop="money">
-          <template slot-scope="scope">
-            <el-input v-model="scope.row.money">
-              <template slot="append">元</template>
-            </el-input>
-          </template>
-        </el-table-column>
-<!--        <el-table-column label="有效时间" align="center" prop="periodStartingTime" width="180" />-->
-        <el-table-column label="创建时间" align="center" prop="createTime" width="180" />
-<!--        <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="['course:period:edit']"-->
-<!--            >修改</el-button>-->
-<!--            <el-button-->
-<!--              size="mini"-->
-<!--              type="text"-->
-<!--              icon="el-icon-edit"-->
-<!--              @click="handleCourse(scope.row)"-->
-<!--              v-hasPermi="['course:period:edit']"-->
-<!--            >课程管理</el-button>-->
-<!--            <el-button-->
-<!--              size="mini"-->
-<!--              type="text"-->
-<!--              icon="el-icon-delete"-->
-<!--              @click="handleDelete(scope.row)"-->
-<!--              v-hasPermi="['course:period:remove']"-->
-<!--            >删除</el-button>-->
-<!--          </template>-->
-<!--        </el-table-column>-->
-      </el-table>
-
-      <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="saveCourseData">保存</el-button>
-      </div>
-    </el-dialog>
-
-    <!-- 添加或修改会员营期对话框-->
+<!--    &lt;!&ndash; 添加或修改会员营期对话框&ndash;&gt;-->
+<!--    <el-dialog title="课程管理" :visible.sync="course.open" width="75%" top="10px" append-to-body style="padding-bottom: 10px">-->
+<!--      <el-row :gutter="10" class="mb8">-->
+<!--        <el-col :span="1.5">-->
+<!--          <el-button-->
+<!--            v-if="(getDiff(course.row.periodStartingTime, course.row.periodEndTime) - course.total) > 0"-->
+<!--            type="primary"-->
+<!--            icon="el-icon-plus"-->
+<!--            size="mini"-->
+<!--            @click="handleAddCourse"-->
+<!--            v-hasPermi="['course:period:add']"-->
+<!--          >添加课程</el-button>-->
+<!--        </el-col>-->
+<!--      </el-row>-->
+<!--      <el-table v-loading="course.loading" :data="course.list">-->
+<!--        <el-table-column label="课程" align="center" prop="courseName" width="180" />-->
+<!--        <el-table-column label="小节" align="center" prop="videoName" />-->
+<!--        <el-table-column label="营期时间" align="center" prop="dayDate" width="150"  />-->
+<!--        <el-table-column label="创建时间" align="center" prop="createTime" width="150" />-->
+<!--      </el-table>-->
+
+<!--      <div slot="footer" class="dialog-footer">-->
+<!--&lt;!&ndash;        <el-button type="primary" @click="saveCourseData">保存</el-button>&ndash;&gt;-->
+<!--      </div>-->
+<!--    </el-dialog>-->
+
+    <!-- 添加课程对话框-->
     <el-dialog title="添加课程" :visible.sync="course.addOpen" width="500px" append-to-body>
       <el-form ref="courseAddForm" :model="course.form" label-width="100px">
         <el-form-item label="课程" prop="courseId">
-          <el-select filterable  v-model="course.form.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(course.form.courseId)" style="width: 100%">
+          <el-select filterable  v-model="course.form.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(course.form.courseId)" style="width: 100%" :value-key="'dictValue'">
             <el-option
               v-for="dict in courseList"
               :key="dict.dictValue"
@@ -357,7 +360,7 @@
           </el-select>
         </el-form-item>
         <el-form-item label="小节" prop="videoId">
-          <el-select filterable  v-model="course.form.videoIds" placeholder="请选择小节" :multiple-limit="getDiff(course.row.periodStartingTime, course.row.periodEndTime) - course.total" multiple clearable size="small" style="width: 100%">
+          <el-select filterable  v-model="course.form.videoIds" placeholder="请选择小节" :multiple-limit="getDiff(course.row.periodStartingTime, course.row.periodEndTime) - course.total" multiple clearable size="small" style="width: 100%" :value-key="'dictValue'">
             <el-option
               v-for="dict in videoList"
               :key="dict.dictValue"
@@ -366,30 +369,211 @@
             />
           </el-select>
         </el-form-item>
+        <el-form-item label="看课时间" prop="timeRange">
+          <el-time-picker
+            is-range
+            v-model="course.form.timeRange"
+            range-separator="至"
+            start-placeholder="开始时间"
+            value-format="HH:mm:ss"
+            end-placeholder="结束时间"
+            placeholder="选择时间范围">
+          </el-time-picker>
+        </el-form-item>
+        <el-form-item label="领取红包时间" prop="lastJoinTime">
+          <el-time-picker
+            v-model="course.form.joinTime"
+            :selectableRange="course.form.timeRange"
+            value-format="HH:mm:ss"
+            placeholder="选择时间范围">
+          </el-time-picker>
+          <p style="color: red;margin: 0;font-size: 12px">超过领取红包时间,只允许看课,不允许领取红包</p>
+        </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitCourseForm">确 定</el-button>
         <el-button @click="closeAddCourse">取 消</el-button>
       </div>
     </el-dialog>
+    <el-dialog title="修改看课时间" :visible.sync="updateCourse.open" width="500px" append-to-body>
+      <el-form ref="courseUpdateForm" :model="updateCourse.form" label-width="100px">
+        <el-form-item label="看课时间" prop="timeRange">
+          <el-time-picker
+            is-range
+            v-model="updateCourse.form.timeRange"
+            range-separator="至"
+            start-placeholder="开始时间"
+            value-format="HH:mm:ss"
+            end-placeholder="结束时间"
+            placeholder="选择时间范围">
+          </el-time-picker>
+        </el-form-item>
+        <el-form-item label="领取红包时间" prop="lastJoinTime">
+          <el-time-picker
+            v-model="updateCourse.form.joinTime"
+            :selectableRange="updateCourse.form.timeRange"
+            value-format="HH:mm:ss"
+            placeholder="选择时间范围">
+          </el-time-picker>
+          <p style="color: red;margin: 0;font-size: 12px">超过领取红包时间,只允许看课,不允许领取红包</p>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitUpdateCourseForm">确 定</el-button>
+        <el-button @click="closeUpdateCourse">取 消</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog title="修改营期时间" :visible.sync="updateDateOpen" width="500px" append-to-body>
+      <el-form ref="courseUpdateForm" :model="form" label-width="100px">
+        <el-form-item label="营期时间" prop="dayDate">
+          <el-date-picker
+            v-model="form.dayDate"
+            :selectableRange="form.dayDate"
+            value-format="yyyy-MM-dd"
+            type="date"
+            placeholder="选择时间">
+          </el-date-picker>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="updateDate">确 定</el-button>
+        <el-button @click="updateDateOpen = false">取 消</el-button>
+      </div>
+    </el-dialog>
 
-
+<!--    <red-packet-->
+<!--      :visible.sync="redPacketVisible"-->
+<!--      :periodId="currentRedPacketData.periodId"-->
+<!--      :videoId="currentRedPacketData.videoId"-->
+<!--      @success="handleRedPacketSuccess"-->
+<!--    />-->
+
+    <!-- 营期相关设置抽屉 -->
+  <el-drawer
+    title="营期相关设置"
+    :visible.sync="periodSettingsVisible"
+    direction="rtl"
+    size="70%"
+    :destroy-on-close="true"
+    append-to-body
+    custom-class="period-settings-drawer"
+  >
+    <div class="drawer-content" style="margin-left: 25px">
+      <el-tabs v-model="activeTab" @tab-click="handleTabClick">
+        <el-tab-pane label="课程管理" name="course">
+          <el-row :gutter="10" class="mb8">
+            <el-col :span="1.5">
+              <el-button
+                v-if="(getDiff(periodSettingsData.periodStartingTime, periodSettingsData.periodEndTime) - course.total) > 0"
+                type="primary"
+                icon="el-icon-plus"
+                size="mini"
+                @click="handleAddCourse"
+                v-hasPermi="['course:period:add']"
+              >添加课程</el-button>
+            </el-col>
+            <el-col :span="1.5">
+              <el-button
+                type="primary"
+                size="mini"
+                :disabled="updateCourse.ids.length <= 0"
+                @click="handleUpdateCourse"
+                v-hasPermi="['course:period:add']"
+              >修改看课时间</el-button>
+            </el-col>
+          </el-row>
+          <el-table v-loading="course.loading" :data="course.list" @selection-change="handleSelectionCourseChange">
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column label="课程" align="center" prop="courseName" width="180" />
+            <el-table-column label="小节" align="center" prop="videoName" />
+            <el-table-column label="营期时间" align="center" prop="dayDate" />
+            <el-table-column label="开始时间" align="center" prop="startDateTime" width="100">
+              <template slot-scope="scope">
+                <el-tag>{{parseTime(scope.row.startDateTime, '{h}:{i}:{s}')}}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="结束时间" align="center" prop="endDateTime" width="100">
+              <template slot-scope="scope">
+                <el-tag type="success">{{parseTime(scope.row.endDateTime, '{h}:{i}:{s}')}}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="领取红包时间" align="center" prop="lastJoinTime" width="100">
+              <template slot-scope="scope">
+                <el-tag type="danger">{{parseTime(scope.row.lastJoinTime, '{h}:{i}:{s}')}}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+              <template slot-scope="scope">
+                <el-button
+                  size="mini"
+                  type="text"
+                  icon="el-icon-top"
+                  @click="handleTop(scope.row)"
+                >上移</el-button>
+              <el-button
+                size="mini"
+                type="text"
+                icon="el-icon-bottom"
+                @click="handleBottom(scope.row)"
+              >下移</el-button>
+<!--              <el-button-->
+<!--                size="mini"-->
+<!--                type="text"-->
+<!--                icon="el-icon-edit"-->
+<!--                @click="handleUpdateDate(scope.row)"-->
+<!--              >修改营期时间</el-button>-->
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-tab-pane>
+        <el-tab-pane label="公司列表" name="company">
+          <red-packet
+            :visible.sync="redPacketVisible"
+            :activeTab="activeTab"
+            :periodId="periodSettingsData.periodId"
+            @success="handleRedPacketSuccess"
+          />
+        </el-tab-pane>
+        <el-tab-pane label="课程统计" name="statistics">
+          <course-statistics
+            :periodId="periodSettingsData.periodId"
+            :active="activeTab === 'statistics'"
+          />
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </el-drawer>
+
+  <batch-red-packet
+    :visible.sync="batchRedPacketVisible"
+    :selected-data="selectedPeriods"
+    @success="handleBatchRedPacketSuccess"
+  />
 
   </div>
 </template>
 
 <script>
-import {addPeriod, delPeriod, exportPeriod, getPeriod, pagePeriod, updatePeriod, getDays, addCourse, updateListCourseData} from "@/api/course/userCoursePeriod";
+import {addPeriod, delPeriod, exportPeriod, getPeriod, pagePeriod, updatePeriod, getDays, addCourse, updateCourseTime, updateCourseDate, updateListCourseData, periodCourseMove, closePeriod} from "@/api/course/userCoursePeriod";
 import {getCompanyList} from "@/api/company/company";
 import { listCamp, addCamp, editCamp, delCamp, copyCamp } from "@/api/course/userCourseCamp";
 import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import RedPacket from './redPacket.vue'
+import BatchRedPacket from './batchRedPacket.vue'
+import CourseStatistics from './statistics.vue'
 
 export default {
   name: "Period",
+  components: {
+    RedPacket,
+    BatchRedPacket,
+    CourseStatistics
+  },
   data() {
     return {
       // 遮罩层
       loading: true,
+      updateDateOpen: false,
       // 左侧遮罩层
       leftLoading: true,
       // 选中数组
@@ -411,6 +595,7 @@ export default {
       videoList: [],
       // 弹出层标题
       title: "",
+      isDisabledDateRange: false, //是否禁用开营日期
       // 是否显示弹出层
       open: false,
       // 查询参数
@@ -445,6 +630,12 @@ export default {
         addOpen: false,
         form: {},
       },
+      updateCourse: {
+        open: false,
+        loading: true,
+        ids: [],
+        form: {},
+      },
       // 表单校验
       rules: {
       },
@@ -473,6 +664,27 @@ export default {
       scrollThrottle: false,
       // 加载更多状态
       loadingMore: false,
+      // 设置红包对话框
+      redPacketVisible: false,
+      periodCompanyList: [],
+      currentRedPacketData: {
+        periodId: '',
+        videoId: ''
+      },
+      // 营期相关设置抽屉
+      periodSettingsVisible: false,
+      activeTab: 'course',
+      periodSettingsData: {},
+      companyList: [],
+      courseDialogVisible: false,
+      redPacketList: [],
+      currentCompany: null,
+      // 选中的营期数据
+      selectedPeriods: [],
+      // 批量设置红包按钮是否禁用
+      batchSetRedPacketDisabled: true,
+      // 批量设置红包弹出框
+      batchRedPacketVisible: false,
     };
   },
   created() {
@@ -549,17 +761,24 @@ export default {
       this.queryParams.companyIdList = [];
       this.handleQuery();
     },
-    // 多选框选中数据
+    /** 多选框选中数据 */
     handleSelectionChange(selection) {
       this.ids = selection.map(item => item.periodId)
       this.single = selection.length!==1
       this.multiple = !selection.length
+      // 更新批量设置红包相关数据
+      this.selectedPeriods = selection;
+      this.batchSetRedPacketDisabled = selection.length === 0;
+    },
+    handleSelectionCourseChange(selection) {
+      this.updateCourse.ids = selection.map(item => item.id)
     },
     /** 新增按钮操作 */
     handleAdd() {
       this.reset();
       this.open = true;
       this.title = "添加会员营期";
+      this.isDisabledDateRange = false;
     },
     /** 修改按钮操作 */
     handleUpdate(row) {
@@ -567,6 +786,13 @@ export default {
       const periodId = row.periodId || this.ids
       getPeriod(periodId).then(response => {
         this.form = response.data;
+        if (this.form.companyId) {
+          this.form.companyId = this.form.companyId.split(',').map(id => Number(id));
+        }
+        // 设置看课时间范围(回显)
+        if (this.form.viewStartTime && this.form.viewEndTime) {
+          this.form.timeRange = [this.form.viewStartTime, this.form.viewEndTime];
+        }
         if(this.form.periodType == 1){
           this.form.dateRange = [this.form.periodStartingTime, this.form.periodEndTime];
         }
@@ -575,6 +801,7 @@ export default {
         }
         this.open = true;
         this.title = "修改会员营期";
+        this.isDisabledDateRange = true;
       });
     },
     /** 提交按钮 */
@@ -582,6 +809,11 @@ export default {
       this.$refs["form"].validate(valid => {
         if (valid) {
           let data = JSON.parse(JSON.stringify(this.form));
+          // 处理看课时间范围
+          if (data.timeRange && data.timeRange.length === 2) {
+            data.viewStartTime = data.timeRange[0];
+            data.viewEndTime = data.timeRange[1];
+          }
           data.companyId = data.companyId.join()
           data.trainingCampId = this.queryParams.trainingCampId
           if (data.periodId != null) {
@@ -606,8 +838,14 @@ export default {
     },
     /** 删除按钮操作 */
     handleDelete(row) {
+      //添加删除判断,只能删除未开始的营期
+      console.log(row.periodStatus)
+      if(row.periodStatus !== 1){
+        this.$message.error('营期处于进行中或者结束,不能删除');
+        return;
+      }
       const periodIds = row.periodId || this.ids;
-      this.$confirm('是否确认删除会员营期编号为"' + periodIds + '"的数据项?', "警告", {
+      this.$confirm('是否确认删除该营期?', "警告", {
           confirmButtonText: "确定",
           cancelButtonText: "取消",
           type: "warning"
@@ -631,6 +869,29 @@ export default {
           this.download(response.msg);
         }).catch(function() {});
     },
+
+    /** 批量设置红包 */
+    handleBatchSetRedPacket() {
+      if (this.selectedPeriods.length === 0) {
+        this.$message.warning('请至少选择一个营期');
+        return;
+      }
+      this.batchRedPacketVisible = true;
+    },
+
+    /** 处理批量设置红包保存 */
+    // handleBatchRedPacketSave(data) {
+    //   // 这里等待接口提供后补充具体实现
+    //   // 示例代码:
+    //   // batchSetRedPacket(data).then(response => {
+    //   //   if (response.code === 200) {
+    //   //     this.$message.success('批量设置成功');
+    //   //     this.getList();
+    //   //   }
+    //   // });
+    //   this.batchRedPacketVisible = false;
+    // },
+
     /** 获取公司下拉列表*/
     getCompanyList() {
       this.loading = true;
@@ -663,7 +924,11 @@ export default {
         dateRange: [],
         date: null,
         days: [],
-        periodEndTime: null
+        periodEndTime: null,
+        timeRange: [], // 看课时间范围
+        viewStartTime: null, // 看课开始时间
+        viewEndTime: null, // 看课结束时间
+        lastJoinTime: null // 领取红包时间
       };
       this.resetForm("form");
     },
@@ -833,6 +1098,7 @@ export default {
     },
     /** 选中训练营 */
     selectCamp(index) {
+      if(index == null || index == undefined) return;
       this.activeCampIndex = index;
       // 加载对应的训练营营期数据
       const selectedCamp = this.campList[index];
@@ -962,17 +1228,42 @@ export default {
         this.course.loading = false;
       });
     },
-    handleAddCourse(){
+    handleAddCourse() {
       this.course.addOpen = true;
       this.course.form = {
-        periodId: this.course.row.periodId,
+        periodId: this.course.queryParams.periodId,
+        courseId: null,
+        videoIds: []
+      };
+      // 重置表单
+      this.$nextTick(() => {
+        if (this.$refs.courseAddForm) {
+          this.$refs.courseAddForm.resetFields();
+        }
+      });
+    },
+    handleUpdateCourse() {
+      this.updateCourse.open = true;
+      this.updateCourse.form = {
+        ids: this.updateCourse.ids,
+        joinTime: [],
       };
     },
-    closeAddCourse(){
+    closeAddCourse() {
       this.course.addOpen = false;
+      this.course.form = {
+        periodId: null,
+        courseId: null,
+        videoIds: []
+      };
+      // 重置表单
+      if (this.$refs.courseAddForm) {
+        this.$refs.courseAddForm.resetFields();
+      }
+    },
+    closeUpdateCourse() {
+      this.course.open = false;
     },
-
-    //查询小节
     courseChange(row){
       this.course.form.videoIds = [];
       videoList(row).then(response => {
@@ -982,6 +1273,10 @@ export default {
     submitCourseForm(){
       this.$refs.courseAddForm.validate(valid => {
         if (valid) {
+          if(this.course.form.timeRange != null && this.course.form.timeRange.length === 2){
+            this.course.form.startTime = this.course.form.timeRange[0];
+            this.course.form.endTime1 = this.course.form.timeRange[1];
+          }
           // 提交数据
           addCourse(this.course.form).then(response => {
             this.$message.success('添加成功');
@@ -992,12 +1287,163 @@ export default {
         }
       });
     },
+    submitUpdateCourseForm(){
+      this.$refs.courseUpdateForm.validate(valid => {
+        if (valid) {
+          if(this.updateCourse.form.timeRange != null && this.updateCourse.form.timeRange.length === 2){
+            this.updateCourse.form.startTime = this.updateCourse.form.timeRange[0];
+            this.updateCourse.form.endTime1 = this.updateCourse.form.timeRange[1];
+          }
+          // 提交数据
+          updateCourseTime(this.updateCourse.form).then(response => {
+            this.$message.success('添加成功');
+            this.updateCourse.open = false;
+            // 重新加载训练营列表
+            this.getCourseList();
+          });
+        }
+      });
+    },
+    updateDate(){
+      updateCourseDate(this.form).then(response => {
+          this.$message.success('修改成功');
+          this.updateDateOpen = false;
+          // 重新加载训练营列表
+          this.getCourseList();
+        });
+    },
     saveCourseData(){
       updateListCourseData(this.course.list).then(response => {
         this.$message.success('保存成功');
         this.getCourseList();
       });
     },
+    setRedPacket(row) {
+      this.currentRedPacketData = {
+        periodId: row.periodId
+        // videoId: row.videoId
+      };
+      this.redPacketVisible = true;
+    },
+    handleRedPacketSuccess() {
+      this.getCourseList();
+    },
+    handlePeriodSettings(row) {
+      this.periodSettingsData = row;
+      this.periodSettingsVisible = true;
+      // 初始化课程列表
+      this.course.queryParams.periodId = row.periodId;
+      // 根据当前激活的tab加载对应数据
+      this.handleTabClick({ name: this.activeTab });
+    },
+    // 结束营期
+    handleClosePeriod(row) {
+      const msg = `注: 1.确认结束营期,该营期的开营结束时间改为当天24点。2.当天正在播放中的课程不变。3.第二天如有未开始的课程,统一改为已结束。是否确认结束 ${row.periodName} 营期吗?`
+      this.$confirm(msg, "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        closePeriod({id: row.periodId}).then(response => {
+          if (response.code === 200) {
+            this.getList()
+          } else {
+            this.$message.error(response.msg)
+          }
+        })
+      }).catch(() => {})
+    },
+    handleBatchRedPacketSuccess() {
+      this.batchRedPacketVisible = false;
+      this.getCourseList();
+    },
+    /** 处理tab切换 */
+    handleTabClick(tab) {
+      if (tab.name === 'course') {
+        this.getCourseList();
+      } else if (tab.name === 'company') {
+        this.redPacketVisible = true;
+      }
+    },
+    /** 上移课程 */
+    handleTop(row) {
+      const currentIndex = this.course.list.findIndex(item => item.id === row.id);
+      if (currentIndex <= 0) {
+        this.$message.warning('已经是第一条数据');
+        return;
+      }
+
+      // 获取上一条数据
+      const prevRow = this.course.list[currentIndex - 1];
+      console.log({
+        id: row.id,
+        targetId: prevRow.id,
+        type: 1 //上移
+      })
+      periodCourseMove({
+        id: row.id,
+        targetId: prevRow.id,
+        type: 1 //上移
+      }).then(response => {
+        if (response.code === 200) {
+          this.$message.success('上移成功');
+          this.getCourseList();
+        } else {
+          this.$message.error(response.msg || '上移失败');
+        }
+      }).catch(() => {
+        this.$message.error('上移失败');
+      });
+    },
+    /** 下移课程 */
+    handleBottom(row) {
+      const currentIndex = this.course.list.findIndex(item => item.id === row.id);
+      if (currentIndex === -1 || currentIndex >= this.course.list.length - 1) {
+        this.$message.warning('已经是最后一条数据');
+        return;
+      }
+
+      // 获取下一条数据
+      const nextRow = this.course.list[currentIndex + 1];
+
+      periodCourseMove({
+        id: row.id,
+        targetId: nextRow.id,
+        type: 2 //下移
+      }).then(response => {
+        if (response.code === 200) {
+          this.$message.success('下移成功');
+          this.getCourseList(); // 重新加载列表
+        } else {
+          this.$message.error(response.msg || '下移失败');
+        }
+      }).catch(() => {
+        this.$message.error('下移失败');
+      });
+    },
+    /** 营期状态格式化 */
+    periodStatusFormatter(row) {
+      const statusMap = {
+        1: '未开始',
+        2: '进行中',
+        3: '已结束'
+      };
+      return statusMap[row.periodStatus] || '未知状态';
+    },
+    /** 开课状态格式化 */
+    courseStatusFormatter(row) {
+      const statusMap = {
+        0: '未开始',
+        1: '进行中',
+        2: '已结束'
+      };
+      return statusMap[row.status] || '未知状态';
+    },
+    /** 营期状态格式化 */
+    handleUpdateDate(row) {
+      this.form = {id: row.id, dayDate: row.dayDate};
+      this.updateDateOpen = true;
+    },
   },
 };
 </script>
@@ -1087,11 +1533,9 @@ export default {
   flex: 1;
   overflow-y: auto;
   padding: 3px;
-  background-color: #f5f7fa;
 }
 
 .camp-item {
-  border-radius: 8px;
   margin-bottom: 5px;
   padding: 15px;
   background-color: #ffffff;
@@ -1112,7 +1556,7 @@ export default {
 
 .camp-item.active {
   background-color: #eaf4ff;
-  border-left: 2px solid #75b8fc;
+  border-left: 1px solid #75b8fc;
 }
 
 .camp-content {
@@ -1215,8 +1659,15 @@ export default {
 }
 
 /* 添加训练营表单样式 */
-.dialog-footer {
+.drawer-footer {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 20px;
+  background: #fff;
   text-align: right;
+  border-top: 1px solid #e8e8e8;
 }
 
 .el-input-number {

+ 173 - 0
src/views/course/userCoursePeriod/redPacket.vue

@@ -0,0 +1,173 @@
+<template>
+  <div>
+    <!-- 公司列表弹窗 -->
+<!--    <el-dialog title="设置红包" :visible.sync="companyDialogVisible" width="800px" append-to-body>-->
+      <el-table :data="companyList" border>
+        <el-table-column type="index" label="序号" width="60" align="center" />
+        <el-table-column label="公司名称" prop="companyName" align="center" />
+        <el-table-column label="操作" align="center" width="120">
+          <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="text"
+              @click="handleInputAmount(scope.row)"
+            >设置红包</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+<!--      <div slot="footer" class="dialog-footer">-->
+<!--      </div>-->
+<!--    </el-dialog>-->
+
+    <!-- 课程红包设置弹窗 -->
+    <el-dialog title="设置红包金额" :visible.sync="courseDialogVisible" width="1200px" append-to-body>
+      <el-table :data="redPacketList" border>
+        <el-table-column type="index" label="序号" width="60" align="center" />
+        <el-table-column label="课程" prop="courseName" align="center" />
+        <el-table-column label="小节" prop="videoName" align="center" />
+        <el-table-column label="营期日期" prop="dayDate" align="center"/>
+        <el-table-column label="红包金额" width="200px" align="center">
+          <template slot-scope="scope">
+            <el-input-number
+              v-model="scope.row.amount"
+              :min="0"
+              :precision="2"
+              :step="0.01"
+              size="small"
+              style="width: 150px"
+            >
+              <template slot="append">元</template>
+            </el-input-number>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="courseDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="handleSave">保 存</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getPeriodCompanyList, getDays, batchSaveRedPacket, getPeriodRedPacketList } from "@/api/course/userCoursePeriod";
+import redPacket from "@/views/course/userCoursePeriod/redPacket.vue";
+
+export default {
+  name: "RedPacket",
+  computed: {
+    // redPacket() {
+    //   return redPacket
+    // }
+  },
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    activeTab: {
+      type: String,
+      default: ''
+    },
+    periodId: {
+      type: [String, Number],
+      default: ''
+    },
+    // videoId: {
+    //   type: [String, Number],
+    //   default: ''
+    // }
+  },
+  data() {
+    return {
+      // companyDialogVisible: false,
+      // activeTab: '',
+      courseDialogVisible: false,
+      companyList: [],
+      redPacketList: [],
+      currentCompany: null
+    };
+  },
+  created() {
+    if(this.activeTab == "company"){
+      this.getCompanyList();
+    }
+  },
+  watch: {
+    activeTab(val) {
+      this.activeTab = val;
+      if (val == "company") {
+        this.getCompanyList();
+      }
+    },
+    companyDialogVisible(val) {
+      if (!val) {
+        this.$emit('update:visible', false);
+      }
+    }
+  },
+  methods: {
+    // 获取公司列表
+    getCompanyList() {
+      getPeriodCompanyList({
+        periodId: this.periodId
+      }).then(response => {
+        this.companyList = response.data || [];
+      });
+    },
+    // 点击录入金额
+    handleInputAmount(row) {
+      this.currentCompany = row;
+      this.courseDialogVisible = true;
+      this.getCourseList();
+    },
+    // 获取课程列表
+    getCourseList() {
+      getPeriodRedPacketList({
+        periodId: this.periodId,
+        companyId: this.currentCompany.companyId
+      }).then(response => {
+        this.redPacketList = (response.data || []).map(item => ({
+          ...item,
+          amount: item.amount || 0
+        }));
+      });
+    },
+    // 保存红包金额
+    handleSave() {
+      const saveData = this.redPacketList
+        .filter(item => item.amount > 0)
+        .map(item => ({
+          companyId: this.currentCompany.companyId,
+          redPacketMoney: item.amount,
+          videoId: item.videoId,
+          periodId: this.periodId,
+          dataType: 2
+        }));
+
+      if (saveData.length === 0) {
+        this.$message.warning('请至少设置一个红包金额');
+        return;
+      }
+
+      batchSaveRedPacket(saveData).then(response => {
+        if (response.code === 200) {
+          this.$message.success('保存成功');
+          this.courseDialogVisible = false;
+          this.$emit('success');
+        } else {
+          this.$message.error(response.msg || "保存失败");
+        }
+      }).catch(error => {
+        this.$message.error("保存失败:" + error.message);
+      });
+    }
+  }
+};
+</script>
+
+<style scoped>
+.el-input-number {
+  width: 100%;
+}
+</style>

+ 338 - 0
src/views/course/userCoursePeriod/statistics.vue

@@ -0,0 +1,338 @@
+<template>
+  <div class="statistics-container">
+    <!-- 营期课程选择 -->
+    <div class="fixed-header">
+      <el-form :inline="true" :model="queryParams" class="demo-form-inline">
+        <el-form-item label="营期课程">
+          <el-select
+            v-model="queryParams.videoIdList"
+            multiple
+            placeholder="请选择营期课程"
+            style="width: 400px"
+          >
+            <el-option
+              v-for="item in courseOptions"
+              :key="item.videoId"
+              :label="item.videoName"
+              :value="item.videoId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">查询</el-button>
+        </el-form-item>
+      </el-form>
+
+      <!-- 统计数据展示 -->
+      <el-row :gutter="20" class="statistics-row">
+        <el-col :span="3">
+          <div class="statistics-item">
+            <div class="statistics-title">完播人数</div>
+            <div class="statistics-value">{{ statistics.courseCompleteNum || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="statistics-item">
+            <div class="statistics-title">观看人数</div>
+            <div class="statistics-value">{{ statistics.courseWatchNum || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="statistics-item">
+            <div class="statistics-title">完播率</div>
+            <div class="statistics-value">{{ statistics.completeRate || '0%' }}</div>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="statistics-item">
+            <div class="statistics-title">观看总次数</div>
+            <div class="statistics-value">{{ statistics.courseWatchTimes || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="statistics-item">
+            <div class="statistics-title">答题总次数</div>
+            <div class="statistics-value">{{ statistics.answerTimes || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="statistics-item">
+            <div class="statistics-title">答题正确总次数</div>
+            <div class="statistics-value">{{ statistics.answerRightTimes || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="statistics-item">
+            <div class="statistics-title">奖励金额总计(元)</div>
+            <div class="statistics-value">{{ statistics.redPacketAmount || 0 }}</div>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+
+    <!-- 列表统计展示 -->
+    <div class="table-wrapper">
+      <el-table v-loading="loading" :data="list" border height="calc(100vh - 450px)">
+        <el-table-column type="index" label="序号" width="50" align="center" fixed/>
+        <el-table-column prop="title" label="课程名称" align="center" min-width="250" fixed/>
+        <el-table-column prop="dayDate" label="营期日期" align="center" min-width="120" fixed/>
+        <el-table-column prop="countDetailsVO.courseWatchTimes" label="观看次数" align="center" min-width="100"/>
+        <el-table-column prop="countDetailsVO.courseCompleteTimes" label="完播次数" align="center" min-width="100"/>
+        <el-table-column prop="countDetailsVO.courseWatchNum" label="观看人数" align="center" min-width="100"/>
+        <el-table-column prop="countDetailsVO.courseCompleteNum" label="完播人数" align="center" min-width="100"/>
+        <el-table-column prop="countDetailsVO.completeRate" label="完播率" align="center" min-width="100">
+          <template slot-scope="scope">
+            {{ scope.row.countDetailsVO.completeRate || 0 }}%
+          </template>
+        </el-table-column>
+        <el-table-column prop="countDetailsVO.answerTimes" label="答题次数" align="center" min-width="100"/>
+        <el-table-column prop="countDetailsVO.answerNum" label="答题人数" align="center" min-width="100"/>
+        <el-table-column prop="countDetailsVO.answerRightNum" label="正确人数" align="center" min-width="100"/>
+        <el-table-column prop="countDetailsVO.answerRightRate" label="正确率" align="center" min-width="100">
+          <template slot-scope="scope">
+            {{ scope.row.countDetailsVO.answerRightRate || 0 }}%
+          </template>
+        </el-table-column>
+        <el-table-column prop="countDetailsVO.redPacketNum" label="答题红包个数" align="center" min-width="120"/>
+        <el-table-column prop="countDetailsVO.redPacketAmount" label="答题红包金额(元)" align="center" min-width="150"/>
+      </el-table>
+
+      <!-- 分页 -->
+      <div class="custom-pagination-container">
+        <pagination
+          v-show="total > 0"
+          :total="total"
+          :page.sync="queryParams.pageNum"
+          :limit.sync="queryParams.pageSize"
+          @pagination="getCountList"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {getDays, periodCountSelect} from "@/api/course/userCoursePeriod";
+
+export default {
+  name: "CourseStatistics",
+  props: {
+    periodId: {
+      type: [String, Number],
+      default: ''
+    },
+    active: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: false,
+      // 总条数
+      total: 0,
+      // 课程选项
+      courseOptions: [],
+      // 统计数据
+      statistics: {
+        courseCompleteNum: 0,
+        courseWatchNum: 0,
+        completeRate: '0%',
+        courseWatchTimes: 0,
+        answerTimes: 0,
+        answerRightTimes: 0,
+        redPacketAmount: 0
+      },
+      // 列表数据
+      list: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        videoIdList: [],
+        // videoId: '',
+        periodId: ''
+      },
+      // 是否已初始化
+      initialized: false
+    };
+  },
+  watch: {
+    periodId: {
+      handler(newVal) {
+        this.queryParams.periodId = newVal;
+        if (this.active && !this.initialized) {
+          this.initializeData();
+        }
+      },
+      immediate: true
+    },
+    active: {
+      handler(newVal) {
+        if (newVal && !this.initialized) {
+          this.initializeData();
+        }
+      },
+      immediate: true
+    }
+  },
+  methods: {
+    /** 初始化数据 */
+    initializeData() {
+      this.getCourseOptions();
+      this.getCountList();
+      this.initialized = true;
+    },
+    /** 获取课程选项 */
+    getCourseOptions() {
+      this.loading = true;
+      getDays(this.queryParams).then(r => {
+        if (r.code === 200) {
+          this.courseOptions = r.rows;
+          this.loading = false;
+        } else {
+          this.$message.error(r.msg || '获取数据失败');
+        }
+        this.loading = false;
+      }).catch(() => {
+        this.loading = false;
+      });
+    },
+    /** 查询按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getCountList();
+    },
+    /** 课程选择变化 */
+    handleCourseChange() {
+    },
+    /** 获取列表数据 */
+    getCountList() {
+      this.loading = true;
+      periodCountSelect(this.queryParams).then(response => {
+        if (response.code === 200) {
+          // 设置列表数据
+          this.list = response.rows;
+          this.total = response.total || 0;
+
+          // 计算总统计数据
+          this.calculateTotalStatistics();
+
+          console.log('列表数据:', this.list);
+        } else {
+          this.$message.error(response.msg || '获取数据失败');
+        }
+        this.loading = false;
+      }).catch(error => {
+        console.error('获取数据失败:', error);
+        this.$message.error('获取数据失败');
+        this.loading = false;
+      });
+    },
+    /** 计算总统计数据 */
+    calculateTotalStatistics() {
+      // 初始化统计数据
+      this.statistics = {
+        courseCompleteNum: 0,
+        courseWatchNum: 0,
+        completeRate: '0%',
+        courseWatchTimes: 0,
+        answerTimes: 0,
+        answerRightTimes: 0,
+        redPacketAmount: 0
+      };
+
+      // 如果没有数据,直接返回
+      if (!this.list || this.list.length === 0) {
+        return;
+      }
+
+      // 遍历列表数据,累加各项统计数据
+      this.list.forEach(item => {
+        const details = item.countDetailsVO || {};
+
+        // 累加各项数据
+        this.statistics.courseCompleteNum += details.courseCompleteNum || 0;
+        this.statistics.courseWatchNum += details.courseWatchNum || 0;
+        this.statistics.courseWatchTimes += details.courseWatchTimes || 0;
+        this.statistics.answerTimes += details.answerTimes || 0;
+        this.statistics.answerRightTimes += details.answerRightNum || 0;
+        this.statistics.redPacketAmount += details.redPacketAmount || 0;
+      });
+
+      // 计算完播率
+      if (this.statistics.courseWatchNum > 0) {
+        const rate = (this.statistics.courseCompleteNum / this.statistics.courseWatchNum * 100).toFixed(2);
+        this.statistics.completeRate = rate + '%';
+      }
+    }
+  }
+};
+</script>
+
+<style scoped>
+.statistics-container {
+  height: 100%;
+  overflow: hidden;
+  position: relative;
+}
+
+.fixed-header {
+  position: sticky;
+  top: 0;
+  z-index: 10;
+  background-color: #fff;
+  padding: 10px 0;
+  border-bottom: 1px solid #EBEEF5;
+}
+
+.table-wrapper {
+  height: calc(100% - 220px);
+  overflow: visible;
+  position: relative;
+}
+
+.custom-pagination-container {
+  padding: 10px 0 12px 0;
+  text-align: right;
+  background-color: #fff;
+  position: relative;
+  z-index: 1;
+}
+
+/* 覆盖原有的pagination-container样式 */
+:deep(.pagination-container) {
+  height: auto !important;
+  margin-bottom: 0 !important;
+  margin-top: 0 !important;
+  padding: 0 !important;
+}
+
+.statistics-row {
+  margin: 20px 0;
+}
+
+.statistics-item {
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  padding: 15px;
+  text-align: center;
+  height: 100px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.statistics-title {
+  font-size: 14px;
+  color: #606266;
+  margin-bottom: 10px;
+}
+
+.statistics-value {
+  font-size: 20px;
+  font-weight: bold;
+  color: #303133;
+}
+</style>

File diff suppressed because it is too large
+ 1261 - 77
src/views/course/videoResource/index.vue


+ 366 - 353
src/views/crm/customer/index.vue

@@ -1,29 +1,15 @@
 <template>
   <div class="app-container">
     <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="公司名" prop="companyId">
-          <el-select filterable  v-model="queryParams.companyId" placeholder="请选择公司名"  @change="companyChange" clearable size="small">
-              <el-option
-                v-for="item in companys"
-                :key="item.companyId"
-                :label="item.companyName"
-                :value="item.companyId"
-              />
-        </el-select>
-      </el-form-item>
-      <el-form-item >
-          <treeselect style="width: 220px" :clearable="false"  v-model="queryParams.deptId"  :options="deptOptions" :show-count="true" placeholder="请选择归属部门" />
-     </el-form-item>
-
-      <el-form-item label="客户编码" prop="customerCode">
+      <el-form-item label="客户编号" prop="customerCode">
         <el-input
           v-model="queryParams.customerCode"
-          placeholder="请输入客户编"
+          placeholder="请输入客户编号"
           clearable
           size="small"
-          @keyup.enter.native="handleQuery" />
+          @keyup.enter.native="handleQuery"
+        />
       </el-form-item>
-
       <el-form-item label="客户名称" prop="customerName">
         <el-input
           v-model="queryParams.customerName"
@@ -33,89 +19,34 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="认领人" prop="companyUserNickName">
-        <el-input
-          v-model="queryParams.companyUserNickName"
-          placeholder="请输入认领人"
-          clearable
-          size="small"
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
       <el-form-item label="手机" prop="mobile">
         <el-input
           v-model="queryParams.mobile"
           placeholder="请输入手机"
           clearable
           size="small"
-          @keyup.enter.native="handleQuery"/>
-      </el-form-item>
-      <el-form-item label="客户状态" prop="status">
-        <el-select v-model="queryParams.status" placeholder="请选择客户状态" clearable size="small">
-           <el-option
-                v-for="item in statusOptions"
-                :key="'status'+item.dictValue"
-                :label="item.dictLabel"
-                :value="item.dictValue"
-              />
-        </el-select>
+          @keyup.enter.native="handleQuery"
+        />
       </el-form-item>
-      <el-form-item label="客户类型" prop="customerType">
-        <el-select multiple v-model="ctsTypeArr" placeholder="请选择客户类型" clearable size="small">
-           <el-option
-                v-for="item in typeOptions"
-                :key="'type'+item.dictValue"
-                :label="item.dictLabel"
-                :value="item.dictValue"
-              />
+      <el-form-item label="客户来源" prop="source">
+        <el-select v-model="queryParams.source" placeholder="请选择客户来源" clearable size="small">
+          <el-option
+            v-for="item in sourceOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
         </el-select>
       </el-form-item>
-      
       <el-form-item label="创建时间" prop="createTime">
         <el-date-picker clearable size="small" style="width: 205.4px"
-          v-model="dateRange"
-          type="daterange"
-          value-format="yyyy-MM-dd"
-          start-placeholder="开始日期" end-placeholder="结束日期">
+                        v-model="dateRange"
+                        type="daterange"
+                        value-format="yyyy-MM-dd"
+                        start-placeholder="开始日期" end-placeholder="结束日期"
+        >
         </el-date-picker>
       </el-form-item>
-      <el-form-item label="领取时间" prop="receiveTimeRange">
-        <el-date-picker
-          style="width:205.4px"
-          clearable size="small"
-          v-model="receiveTimeRange"
-          type="daterange"
-          value-format="yyyy-MM-dd"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期">
-        </el-date-picker>
-      </el-form-item>
-      <el-form-item label="客户来源" prop="source">
-        <el-select multiple v-model="sourceArr" placeholder="请选择客户来源" clearable size="small">
-           <el-option
-                v-for="item in sourceOptions"
-                :key="'source'+item.dictValue"
-                :label="item.dictLabel"
-                :value="item.dictValue"
-              />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="客户标签" prop="tags">
-        <el-select multiple v-model="tagIds" placeholder="请选择客户标签" clearable size="small">
-           <el-option
-                v-for="item in tagsOptions"
-                :key="'tags'+item.dictValue"
-                :label="item.dictLabel"
-                :value="item.dictValue"
-              />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="是否下单" prop="isBuy">
-            <el-select style="width:220px" filterable v-model="queryParams.isBuy" placeholder="请选择是否下单" clearable size="small">
-              <el-option key="1"  label="已下单" value="1" />
-              <el-option key="0"  label="未下单" value="0" />
-            </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>
@@ -133,6 +64,15 @@
           v-hasPermi="['crm:customer:editSource']"
         >修改客户来源</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['crm:customer:addLine']"
+        >新增</el-button>
+      </el-col>
       <el-col :span="1.5">
         <el-button
           type="success"
@@ -140,7 +80,7 @@
           size="mini"
           :disabled="single"
           @click="handleUpdate"
-          v-hasPermi="['crm:customer:edit']"
+          v-hasPermi="['crm:customer:editLine']"
         >修改</el-button>
       </el-col>
       <el-col :span="1.5">
@@ -150,87 +90,101 @@
           size="mini"
           :disabled="multiple"
           @click="handleDelete"
-          v-hasPermi="['crm:customer:remove']"
+          v-hasPermi="['crm:customer:removeLine']"
         >删除</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          v-if="queryParams.status=='0'"
+          type="success"
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="multiple"
+          @click="handleAssign"
+          v-hasPermi="['crm:customer:assign']"
+        >分配线索客户</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          size="mini"
+          @click="handleAssignList"
+          v-hasPermi="['crm:customerAssign:list']"
+        >分配历史记录</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          icon="el-icon-upload2"
+          size="mini"
+          @click="handleImport"
+          v-hasPermi="['crm:customer:importLine']"
+        >导入线索客户</el-button>
+      </el-col>
       <el-col :span="1.5">
         <el-button
           type="warning"
           icon="el-icon-download"
           size="mini"
           @click="handleExport"
-          v-hasPermi="['crm:customer:export']"
+          v-hasPermi="['crm:customer:exportLine']"
         >导出</el-button>
       </el-col>
-	  <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
-
-    <el-table  height="500" border v-loading="loading" :data="customerList" @selection-change="handleSelectionChange">
+    <el-tabs type="card" v-model="activeName" @tab-click="handleClick">
+      <el-tab-pane label="待分配" name="0"></el-tab-pane>
+      <el-tab-pane label="已分配" name="1"></el-tab-pane>
+    </el-tabs>
+    <el-table border height="500" border v-loading="loading" :data="customerList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="ID" align="center" prop="customerId" />
       <el-table-column label="所属公司" align="center" prop="companyName" />
-      <el-table-column label="客户编码" align="center" prop="customerCode" />
+      <el-table-column label="客户编号" width="120"  align="center" prop="customerCode" />
       <el-table-column label="客户名称" align="center" prop="customerName" />
       <el-table-column label="手机" align="center" prop="mobile" />
       <el-table-column  label="性别" align="center" prop="sex">
         <template slot-scope="scope">
-            <el-tag prop="sex" v-for="(item, index) in sexOptions" :key="'sex'+index"   v-if="scope.row.sex==item.dictValue">{{item.dictLabel}}</el-tag>
+          <el-tag prop="status" v-for="(item, index) in sexOptions"    v-if="scope.row.sex==item.dictValue">{{item.dictLabel}}</el-tag>
         </template>
       </el-table-column>
       <el-table-column label="微信号" align="center" prop="weixin" />
       <el-table-column label="所在地" align="center" prop="address" />
-      <el-table-column label="标签" align="center" prop="tags" />
+      <el-table-column width="105" label="创建时间" align="center" prop="createTime" />
       <el-table-column  label="客户来源" align="center" prop="source">
         <template slot-scope="scope">
-            <el-tag prop="source" v-for="(item, index) in sourceOptions"  :key="'source'+index"    v-if="scope.row.source==item.dictValue">{{item.dictLabel}}</el-tag>
+          <el-tag prop="source" v-for="(item, index) in sourceOptions"   v-if="scope.row.source==item.dictValue">{{item.dictLabel}}</el-tag>
         </template>
       </el-table-column>
-
-      <el-table-column  label="客户类型" align="center" prop="customerType">
-        <template slot-scope="scope">
-            <el-tag prop="customerType" v-for="(item, index) in typeOptions" :key="'customerType'+index"   v-if="scope.row.customerType==item.dictValue">{{item.dictLabel}}</el-tag>
-        </template>
-      </el-table-column>
-
-      <el-table-column  label="客户状态" align="center" prop="status">
-        <template slot-scope="scope">
-            <el-tag prop="status" v-for="(item, index) in statusOptions"  :key="'status'+index"  v-if="scope.row.status==item.dictValue">{{item.dictLabel}}</el-tag>
-        </template>
-      </el-table-column>
-
-      <el-table-column label="认领人" align="center" prop="companyUserNickName" />
-      <el-table-column label="领取时间" align="center" prop="receiveTime" />
-      <el-table-column label="创建时间" align="center" prop="createTime" />
-      <el-table-column label="最后一次跟进时间" align="center" prop="lastTime" width="180">
-      </el-table-column>
-      <el-table-column label="入公海时间" align="center" prop="poolTime" width="180">
-      </el-table-column>
-
-      <el-table-column label="操作" fixed="right" width="180px" align="center" class-name="small-padding fixed-width">
+      <el-table-column label="流量来源" align="center" prop="trafficSource" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="操作"   align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
           <el-button
             size="mini"
             type="text"
             icon="el-icon-edit"
             @click="handleUpdate(scope.row)"
-            v-hasPermi="['crm:customer:edit']"
+            v-hasPermi="['crm:customer:editLine']"
           >修改</el-button>
-          <el-button
+          <!-- <el-button
             size="mini"
             type="text"
-            @click="handleShow(scope.row)"
-            v-hasPermi="['crm:customer:query']"
-          >查看</el-button>
+            icon="el-icon-edit"
+            @click="handleAssign(scope.row)"
+            v-hasPermi="['crm:customer:assign']"
+          >分配</el-button> -->
           <el-button
             size="mini"
             type="text"
             icon="el-icon-delete"
             @click="handleDelete(scope.row)"
-            v-hasPermi="['crm:customer:remove']"
+            v-hasPermi="['crm:customer:removeLine']"
           >删除</el-button>
         </template>
       </el-table-column>
     </el-table>
+
     <pagination
       v-show="total>0"
       :total="total"
@@ -241,16 +195,13 @@
     />
 
     <!-- 添加或修改客户对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="客户编码" prop="customerCode">
-          <el-input v-model="form.customerCode" :disabled="form.customerId!=null" placeholder="请输入客户编码" />
-        </el-form-item>
+    <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
         <el-form-item label="客户名称" prop="customerName">
           <el-input v-model="form.customerName" placeholder="请输入客户名称" />
         </el-form-item>
         <el-form-item label="手机" prop="mobile">
-          <el-input  maxlength="11" v-model="form.mobile" placeholder="请输入手机" />
+          <el-input maxlength="11"  v-model="form.mobile" placeholder="请输入手机" />
         </el-form-item>
         <el-form-item label="性别" prop="sex">
           <el-radio-group v-model="form.sex">
@@ -258,14 +209,14 @@
           </el-radio-group>
         </el-form-item>
         <el-form-item label="微信号" prop="weixin">
-          <el-input v-model="form.weixin" placeholder="请输入性别" />
+          <el-input v-model="form.weixin" placeholder="请输入微信号" />
         </el-form-item>
         <el-form-item label="所在地区" prop="address">
           <el-cascader
-          ref="citySelect"
-          v-model="cityIds"
-          :options="citys"
-          @change="handleCityChange"></el-cascader>
+            ref="citySelect"
+            v-model="cityIds"
+            :options="citys"
+            @change="handleCityChange"></el-cascader>
         </el-form-item>
         <el-form-item label="详细地址" prop="detailAddress">
           <el-input v-model="form.detailAddress" placeholder="请输入详细地址" />
@@ -273,28 +224,13 @@
         <el-form-item label="客户来源" prop="source">
           <el-select v-model="form.source" placeholder="请选择客户来源" clearable size="small">
             <el-option
-                  v-for="item in sourceOptions"
-                  :key="item.dictValue"
-                  :label="item.dictLabel"
-                  :value="item.dictValue"
-                />
+              v-for="item in sourceOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
           </el-select>
         </el-form-item>
-        <el-form-item label="客户类型" prop="customerType">
-          <el-select v-model="form.customerType" placeholder="请选择客户类型" clearable size="small">
-            <el-option
-                  v-for="item in typeOptions"
-                  :key="item.dictValue"
-                  :label="item.dictLabel"
-                  :value="item.dictValue"
-                />
-          </el-select>
-      </el-form-item>
-        <el-form-item label="客户状态">
-          <el-radio-group v-model="form.status">
-            <el-radio :label="item.dictValue" v-for="item in statusOptions">{{item.dictLabel}}</el-radio>
-          </el-radio-group>
-        </el-form-item>
         <el-form-item label="标签" prop="tags">
           <el-tag
             :key="tag"
@@ -317,15 +253,37 @@
           <el-button v-else class="button-new-tag" size="small" @click="showInput">添加标签+</el-button>
           <el-select @change="tagsChange"  v-model="tagId" placeholder="选择客户来源" style="width:140px;margin-left: 5px;" clearable size="small">
             <el-option
-                  v-for="item in tagsOptions"
-                  :key="item.dictValue"
-                  :label="item.dictLabel"
-                  :value="item.dictValue"
-                />
+              v-for="item in tagsOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
           </el-select>
         </el-form-item>
+        <el-form-item label="进线日期" prop="registerDate">
+          <el-date-picker
+            value-format="yyyy-MM-dd"
+            v-model="form.registerDate"
+            type="date"
+            placeholder="请选择进线日期">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="进线链接" prop="registerLinkUrl">
+          <el-input v-model="form.registerLinkUrl" placeholder="请输入进线链接" />
+        </el-form-item>
+        <el-form-item label="进线客户详情" prop="registerDesc">
+          <el-input v-model="form.registerDesc" placeholder="请输入进线客户详情" />
+        </el-form-item>
+        <el-form-item label="进线填写时间" prop="registerSubmitTime">
+          <el-date-picker
+            value-format="yyyy-MM-dd HH:mm:ss"
+            v-model="form.registerSubmitTime"
+            type="datetime"
+            placeholder="请选择进线填写时间">
+          </el-date-picker>
+        </el-form-item>
         <el-form-item label="备注" prop="remark">
-          <el-input :rows="2" v-model="form.remark" type="textarea" placeholder="请输入内容" />
+          <el-input type="textarea" :rows="2" v-model="form.remark"   placeholder="请输入备注" />
         </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
@@ -333,68 +291,113 @@
         <el-button @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
-    <el-drawer
-     size="75%"
-      :title="show.title" :visible.sync="show.open">
-        <customer-details  ref="customerDetails" />
-    </el-drawer>
+    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
+      <el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">
+          将文件拖到此处,或
+          <em>点击上传</em>
+        </div>
+        <div class="el-upload__tip" slot="tip">
+          <el-link type="info" style="font-size:12px" @click="importTemplate">下载模板</el-link>
+        </div>
+        <div class="el-upload__tip" style="color:red" slot="tip">提示:仅允许导入“xls”或“xlsx”格式文件!</div>
+      </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitFileForm">确 定</el-button>
+        <el-button @click="upload.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog title="导入结果" :close-on-press-escape="false" :close-on-click-modal="false" :visible.sync="importMsgOpen" width="500px" append-to-body>
+      <div class="import-msg" v-html="importMsg">
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="assign.title" :visible.sync="assign.open" @close="closeAssignPop" width="900px" append-to-body>
+      <assign-company  ref="assignCompany" @close="closeAssign"   />
+    </el-dialog>
+
+    <el-dialog :title="assignList.title" :visible.sync="assignList.open" width="1200px" append-to-body>
+      <customer-assign-list  ref="assignList" @close="closeAssignList"></customer-assign-list>
+    </el-dialog>
 
     <el-dialog :title="source.title" :visible.sync="source.open" width="600px" append-to-body>
       <edit-customer-source  ref="editSource" @close="closeSource"></edit-customer-source>
     </el-dialog>
+
   </div>
 </template>
 
 <script>
-import { listCustomer,getCustomer,addCustomer,updateCustomer,delCustomer,exportCustomer  } from "@/api/crm/customer";
-import { getCompanyList } from "@/api/company/company";
-import customerDetails from '../components/customerDetails.vue';
-import editCustomerSource from '../components/editCustomerSource.vue';
+import { assignCustomer,importLineTemplate,listLineCustomer, getLineCustomer, delLineCustomer, addLineCustomer, updateLineCustomer, exportLineCustomer } from "@/api/crm/customer";
 import {getCitys} from "@/api/store/city";
-import { throwStatement } from "@babel/types";
+import { getToken } from "@/utils/auth";
+import { isAdmin } from "@/api/system/user";
+import editCustomerSource from '../components/editCustomerSource.vue';
 
-import { treeselect } from "@/api/company/companyDept";
-import Treeselect from "@riophae/vue-treeselect";
-import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import customerAssignList from '../components/customerAssignList.vue';
+import assignCompany from '../components/assignCompany.vue';
 
 export default {
-   watch: {
-    // 监听deptId
-    'deptId': 'currDeptChange'
-  },
   name: "Customer",
-  components: { customerDetails,editCustomerSource,Treeselect },
+  components: { customerAssignList,assignCompany,editCustomerSource },
+
   data() {
     return {
-      source:{
-        title:"修改客户来源",
+      assignList:{
+        title:"分配历史记录",
         open:false,
       },
-      deptOptions:[],
-      receiveTimeRange:[],
+      importMsgOpen:false,
+      importMsg:"",
       tagId:null,
-      deptId:undefined,
-      companyId:undefined,
+      activeName:"0",
       tagsOptions:[],
-      ctsTypeArr:[],
-      sourceArr:[],
-      dateRange:[],
+      companys:[],
+      noAssignCount:0,
+      assignCount:0,
+      source:{
+        title:"修改客户来源",
+        open:false,
+      },
+      assign:{
+        title:"分配客户",
+        open:false,
+      },
+      companySelect:{
+        title:"选择公司",
+        open:false,
+      },
+      assignForm: {
+        companyId:null,
+      },
+      // 表单校验
+      assignRules: {
+        companyId: [
+          { required: true, message: "公司不能为空", trigger: "blur" }
+        ],
+      },
+      // 用户导入参数
+      upload: {
+        // 是否显示弹出层(用户导入)
+        open: false,
+        // 弹出层标题(用户导入)
+        title: "",
+        // 是否禁用上传
+        isUploading: false,
+        // 设置上传的请求头部
+        headers: { Authorization: "Bearer " + getToken() },
+        // 上传的地址
+        url: process.env.VUE_APP_BASE_API + "/crm/customer/importLineData",
+      },
+      pageSizes: [10, 20, 30, 50,100,500 ],
       cityIds:[],
       citys:[],
       tags:[],
-      tagIds:[],
       inputVisible: false,
       inputValue: '',
-      statusOptions:[],
-      typeOptions:[],
       sourceOptions:[],
       sexOptions:[],
-      pageSizes: [10, 20, 30, 50,100,500 ],
-      show:{
-        title:"客户详情",
-        open:false,
-      },
-      companys:[],
       // 遮罩层
       loading: true,
       // 选中数组
@@ -409,10 +412,12 @@ export default {
       total: 0,
       // 客户表格数据
       customerList: [],
+      dateRange:[],
       // 弹出层标题
       title: "",
       // 是否显示弹出层
       open: false,
+      isAdmin:false,
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -431,7 +436,7 @@ export default {
         detailAddress: null,
         lng: null,
         lat: null,
-        status: null,
+        status: "0",
         isReceive: null,
         deptId: null,
         isDel: null,
@@ -444,18 +449,16 @@ export default {
         tags: null
       },
       // 表单参数
-      form: {},
+      form: {
+        province:null,
+        city:null,
+        district:null,
+      },
       // 表单校验
       rules: {
-        // customerName: [
-        //   { required: true, message: "客户名称不能为空", trigger: "blur" }
-        // ],
         mobile: [
           { required: true, message: "手机号不能为空", trigger: "blur" }
         ],
-        // sex: [
-        //   { required: true, message: "性别不能为空", trigger: "blur" }
-        // ],
         source: [
           { required: true, message: "客户来源不能为空", trigger: "blur" }
         ],
@@ -463,24 +466,21 @@ export default {
     };
   },
   created() {
-    this.getDicts("crm_customer_tag").then((response) => {
-      this.tagsOptions = response.data;
-    });
     this.getDicts("crm_customer_source").then((response) => {
       this.sourceOptions = response.data;
     });
     this.getDicts("common_sex").then((response) => {
       this.sexOptions = response.data;
     });
-    this.getDicts("crm_customer_status").then((response) => {
-      this.statusOptions = response.data;
-    });
-    this.getDicts("crm_customer_type").then((response) => {
-      this.typeOptions = response.data;
+    this.getDicts("crm_customer_tag").then((response) => {
+      this.tagsOptions = response.data;
     });
-    getCompanyList().then(response => {
-      this.companys = response.data;
+
+    isAdmin().then(res => {
+      this.isAdmin=res.data;
+      this.isAdmin=false;
     });
+
     this.getCitys();
     this.getList();
   },
@@ -497,24 +497,23 @@ export default {
       this.source.open=false;
       this.getList();
     },
-    handleClose(tag) {
-        this.tags.splice(this.tags.indexOf(tag), 1);
-        this.form.tags=this.tags.toString();
+    handleAssignList(){
+      this.assignList.open=true;
+      var that=this;
+      setTimeout(() => {
+        that.$refs.assignList.getData();
+      }, 200);
     },
-    showInput() {
-      this.inputVisible = true;
-      this.$nextTick(_ => {
-        this.$refs.saveTagInput.$refs.input.focus();
-      });
+    closeAssign(){
+      this.assign.open=false;
+      this.getList();
     },
-    handleInputConfirm() {
-      let inputValue = this.inputValue;
-      if (inputValue) {
-        this.tags.push(inputValue);
-      }
-      this.inputVisible = false;
-      this.inputValue = '';
-      this.form.tags=this.tags.toString();
+    closeAssignPop(){
+      this.$refs.assignCompany.closeAction();
+    },
+    closeAssignList(){
+      this.assignList.open=false;
+      this.getList();
     },
     tagsChange(e){
       var item=this.tagsOptions.find(val => val.dictValue === e);
@@ -522,21 +521,26 @@ export default {
       this.tags.push(item.dictLabel);
       this.form.tags=this.tags.toString();
     },
+    handleClick(tab, event) {
+      this.activeName=tab.name;
+      this.queryParams.status=tab.name
+      this.getList();
+    },
     handleCityChange(value) {
-      console.log(value);
       var nodes=this.$refs.citySelect.getCheckedNodes();
+      console.log(value);
       this.form.address=nodes[0].pathLabels[0]+"-"+nodes[0].pathLabels[1]+"-"+nodes[0].pathLabels[2];
       this.form.cityIds=value.toString();
     },
     getCitys(){
-        getCitys().then(res => {
-          this.loading = false;
-          this.citys=res.data;
-        })
+      getCitys().then(res => {
+        this.loading = false;
+        this.citys=res.data;
+      })
     },
     handleClose(tag) {
-        this.tags.splice(this.tags.indexOf(tag), 1);
-        this.form.tags=this.tags.toString();
+      this.tags.splice(this.tags.indexOf(tag), 1);
+      this.form.tags=this.tags.toString();
     },
     showInput() {
       this.inputVisible = true;
@@ -553,43 +557,10 @@ export default {
       this.inputValue = '';
       this.form.tags=this.tags.toString();
     },
-    handleShow(row){
-      var that=this;
-      that.show.open=true;
-      setTimeout(() => {
-          that.$refs.customerDetails.getDetails(row.customerId);
-      }, 200);
-    },
     /** 查询客户列表 */
     getList() {
       this.loading = true;
-      if(this.receiveTimeRange!=null&&this.receiveTimeRange.length==2){
-        this.queryParams.receiveTimeRange=this.receiveTimeRange[0]+"--"+this.receiveTimeRange[1]
-      }
-      else{
-        this.queryParams.receiveTimeRange=null;
-      }
-      if(this.ctsTypeArr.length>0){
-        this.queryParams.customerType=this.ctsTypeArr.toString();
-      }
-      else{
-        this.queryParams.customerType=null
-      }
-
-      if(this.sourceArr.length>0){
-        this.queryParams.source=this.sourceArr.toString();
-      }
-      else{
-        this.queryParams.source=null
-      }
-      if(this.tagIds.length>0){
-        this.queryParams.tags=this.tagIds.toString();
-      }
-      else{
-        this.queryParams.tags=null
-      }
-
-      listCustomer(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
+      listLineCustomer(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
         this.customerList = response.rows;
         this.total = response.total;
         this.loading = false;
@@ -621,7 +592,7 @@ export default {
         lat: null,
         createTime: null,
         updateTime: null,
-        status: "1",
+        status: 0,
         isReceive: null,
         deptId: null,
         isDel: null,
@@ -653,31 +624,68 @@ export default {
       this.single = selection.length!==1
       this.multiple = !selection.length
     },
+    handleAssign(){
+      //if(this.isAdmin){
+      var that=this
+      this.assign.open = true;
+      this.assign.title = "分配线索客户";
+      setTimeout(() => {
+        that.$refs.assignCompany.init(this.ids);
+      }, 200);
+      // }else{
+      //      this.msgError("无权限操作!");
+      // }
+
+      // //获取所有CUSTOMER列表
+      // var cutsomers=[];
+      // this.customerList.forEach(function(value,index,array){
+      //   that.ids.forEach(function(id,index,array){
+      //     if(id==value.customerId){
+      //       var data={customerId:value.customerId,customerName:value.customerName}
+      //       cutsomers.push(data)
+      //     }
+      //   })
+      // });
+      // this.assignForm.customers=cutsomers;
+      // this.assignCount=this.assignForm.customers.length;
+    },
+    submitAssignForm() {
+      if(this.assignForm.customers.length==0){
+        this.msgError("请选择客户");
+        return;
+      }
+      this.$refs["assignForm"].validate(valid => {
+        if (valid) {
+          var ids=[];
+          this.assignForm.customers.forEach(function(value,index,array){
+            ids.push(value.customerId)
+          });
+          this.assignForm.customerIds=ids;
+          assignCustomer(this.assignForm).then(response => {
+            if (response.code === 200) {
+              this.msgSuccess("分配成功");
+              this.assign.open = false;
+              this.getList();
+            }
+          });
+        }
+      });
+    },
     /** 新增按钮操作 */
     handleAdd() {
       this.reset();
       this.open = true;
-      this.title = "添加客户";
+      this.title = "添加线索客户";
     },
     /** 修改按钮操作 */
     handleUpdate(row) {
       this.reset();
-      const customerId = row.customerId || this.ids
       var that=this;
-      getCustomer(customerId).then(response => {
-        this.form = response.customer;
-        if(this.form.status!=null){
-          this.form.status = this.form.status.toString();
-        }
-        if(this.form.customerType!=null){
-          this.form.customerType = this.form.customerType.toString();
-        }
-        if(this.form.sex!=null){
-          this.form.sex = this.form.sex.toString();
-        }
-        if(this.form.source!=null){
-          this.form.source = this.form.source.toString();
-        }
+      const customerId = row.customerId || this.ids
+      getLineCustomer(customerId).then(response => {
+        this.form = response.data;
+        this.form.sex = response.data.sex.toString();
+        this.form.source = response.data.source.toString();
         if(this.form.tags!=null){
           this.tags = this.form.tags.split(",")
         }
@@ -685,12 +693,12 @@ export default {
           var ids=this.form.cityIds.split(",");
           this.cityIds=[];
           ids.forEach(element => {
-              var id=parseInt(element);
-              that.cityIds.push(id)
+            var id=parseInt(element);
+            that.cityIds.push(id)
           });
         }
         this.open = true;
-        this.title = "修改客户";
+        this.title = "修改线索客户";
       });
     },
     /** 提交按钮 */
@@ -698,7 +706,7 @@ export default {
       this.$refs["form"].validate(valid => {
         if (valid) {
           if (this.form.customerId != null) {
-            updateCustomer(this.form).then(response => {
+            updateLineCustomer(this.form).then(response => {
               if (response.code === 200) {
                 this.msgSuccess("修改成功");
                 this.open = false;
@@ -706,7 +714,7 @@ export default {
               }
             });
           } else {
-            addCustomer(this.form).then(response => {
+            addLineCustomer(this.form).then(response => {
               if (response.code === 200) {
                 this.msgSuccess("新增成功");
                 this.open = false;
@@ -721,72 +729,77 @@ export default {
     handleDelete(row) {
       const customerIds = row.customerId || this.ids;
       this.$confirm('是否确认删除客户编号为"' + customerIds + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return delCustomer(customerIds);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("删除成功");
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delLineCustomer(customerIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
     },
     /** 导出按钮操作 */
     handleExport() {
-      //const queryParams = this.queryParams;
-      const queryParams=this.addDateRange(this.queryParams, this.dateRange)
-      this.$confirm('是否确认导出客户数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return exportCustomer(queryParams);
-        }).then(response => {
-          this.download(response.msg);
-        }).catch(function() {});
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有客户数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return exportLineCustomer(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+      }).catch(function() {});
     },
-    getTreeselect() {
-        var that=this;
-        var param={companyId:this.companyId}
-        treeselect(param).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;
-          }
-        });
+    /** 导入按钮操作 */
+    handleImport() {
+      this.upload.title = "线索客户导入";
+      this.upload.open = true;
     },
-    companyChange(val){
-      console.log(val);
-      this.companyId=val;
-      this.getTreeselect();
+    /** 下载模板操作 */
+    importTemplate() {
+      importLineTemplate().then((response) => {
+        this.download(response.msg);
+      });
+    },
+    // 文件上传中处理
+    handleFileUploadProgress(event, file, fileList) {
+      this.upload.isUploading = true;
     },
-     currDeptChange(val){
-          console.log(val)
-          this.queryParams.deptId=val;
-          this.getList();
+    // 文件上传成功处理
+    handleFileSuccess(response, file, fileList) {
+      this.upload.open = false;
+      this.upload.isUploading = false;
+      this.$refs.upload.clearFiles();
+      this.importMsgOpen=true;
+      this.importMsg=response.msg
+      this.getList();
     },
+    // 提交上传文件
+    submitFileForm() {
+      this.$refs.upload.submit();
+    }
   }
 };
 </script>
 <style>
-  .el-tag + .el-tag {
-    margin-left: 10px;
-  }
-  .button-new-tag {
-    margin-left: 10px;
-    height: 32px;
-    line-height: 30px;
-    padding-top: 0;
-    padding-bottom: 0;
-  }
-  .input-new-tag {
-    width: 90px;
-    margin-left: 10px;
-    vertical-align: bottom;
-  }
-  .el-dialog__wrapper{
-    z-index: 100000;
-  }
-
+.el-tag + .el-tag {
+  margin-left: 10px;
+}
+.button-new-tag {
+  margin-left: 10px;
+  height: 32px;
+  line-height: 30px;
+  padding-top: 0;
+  padding-bottom: 0;
+}
+.input-new-tag {
+  width: 90px;
+  margin-left: 10px;
+  vertical-align: bottom;
+}
+.el-dialog__wrapper{
+  z-index: 100000;
+}
 </style>

+ 28 - 28
src/views/crm/event/index.vue

@@ -17,10 +17,10 @@
       </el-form-item>
       <el-form-item label="开始时间" prop="eventTime">
         <el-date-picker clearable size="small" style="width: 200px"
-          v-model="queryParams.eventTime"
-          type="date"
-          value-format="yyyy-MM-dd"
-          placeholder="选择开始时间">
+                        v-model="queryParams.eventTime"
+                        type="date"
+                        value-format="yyyy-MM-dd"
+                        placeholder="选择开始时间">
         </el-date-picker>
       </el-form-item>
       <el-form-item label="销售ID" prop="companyUserId">
@@ -100,10 +100,10 @@
           v-hasPermi="['crm:event:export']"
         >导出</el-button>
       </el-col>
-	  <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table v-loading="loading" :data="eventList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="eventList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="关联ID" align="center" prop="eventId" />
       <el-table-column label="标题" align="center" prop="title" />
@@ -137,7 +137,7 @@
         </template>
       </el-table-column>
     </el-table>
-    
+
     <pagination
       v-show="total>0"
       :total="total"
@@ -159,10 +159,10 @@
         </el-form-item>
         <el-form-item label="开始时间" prop="eventTime">
           <el-date-picker clearable size="small" style="width: 200px"
-            v-model="form.eventTime"
-            type="date"
-            value-format="yyyy-MM-dd"
-            placeholder="选择开始时间">
+                          v-model="form.eventTime"
+                          type="date"
+                          value-format="yyyy-MM-dd"
+                          placeholder="选择开始时间">
           </el-date-picker>
         </el-form-item>
         <el-form-item label="销售ID" prop="companyUserId">
@@ -348,28 +348,28 @@ export default {
     handleDelete(row) {
       const eventIds = row.eventId || this.ids;
       this.$confirm('是否确认删除代办事项编号为"' + eventIds + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return delEvent(eventIds);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("删除成功");
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delEvent(eventIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
     },
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有代办事项数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return exportEvent(queryParams);
-        }).then(response => {
-          this.download(response.msg);
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return exportEvent(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+      }).catch(function() {});
     }
   }
 };

+ 156 - 70
src/views/fs/user/index.vue

@@ -25,6 +25,13 @@
       <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-button
+          type="primary"
+          icon="el-icon-user"
+          size="mini"
+          @click="handleChangeCompanyUser"
+          :disabled="multiple"
+        >更换会员归属</el-button>
       </el-form-item>
     </el-form>
 
@@ -44,6 +51,13 @@
           </el-tag>
         </template>
       </el-table-column>
+      <el-table-column label="是否宠粉" align="center" prop="isRepeat">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.isRepeat === 1 ? 'success' : 'danger'">
+            {{ scope.row.status === 1 ? '是' : '否' }}
+          </el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="看课数量" align="center" prop="watchCourseCount" />
       <el-table-column label="参与营期数量" align="center" prop="partCourseCount" />
       <el-table-column label="所属销售" align="center" prop="companyUserNickName" />
@@ -58,17 +72,17 @@
         </template>
       </el-table-column>
       <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
-<!--      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">-->
-<!--        <template slot-scope="scope">-->
-<!--          <el-button-->
-<!--            size="mini"-->
-<!--            type="text"-->
-<!--            icon="el-icon-delete"-->
-<!--            @click="handleDelete(scope.row)"-->
-<!--            v-hasPermi="['fs:user:remove']"-->
-<!--          >删除</el-button>-->
-<!--        </template>-->
-<!--      </el-table-column>-->
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleQwAccount(scope.row)"
+            v-hasPermi="['fs:user:remove']"
+          >查看企微账号</el-button>
+        </template>
+      </el-table-column>
     </el-table>
 
     <pagination
@@ -103,12 +117,32 @@
         <el-button @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
+
+    <!-- 更换会员归属对话框 -->
+    <el-dialog title="更换会员归属" :visible.sync="changeCompanyUserOpen" width="500px" append-to-body>
+      <el-form ref="changeCompanyUserForm" :model="changeCompanyUserForm" :rules="changeCompanyUserRules" label-width="100px">
+        <el-form-item label="选择销售" prop="companyUserId">
+          <el-select v-model="changeCompanyUserForm.companyUserId" placeholder="请选择销售" style="width: 100%" @change="handleCompanyUserChange">
+            <el-option
+              v-for="item in companyUserOptions"
+              :key="item.userId"
+              :label="item.nickName + '_' + item.userName"
+              :value="item.userId"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitChangeCompanyUserForm">确 定</el-button>
+        <el-button @click="cancelChangeCompanyUser">取 消</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import { listUser, getUser, delUser, addUser, updateUser, exportUser } from "@/api/fs/user";
-
+import { listUser, delUser, addUser, updateUser, exportUser } from "@/api/fs/user";
+import { getCompanyUserList, changeCompanyUser } from '@/api/company/companyUser';
 export default {
   name: "User",
   data() {
@@ -155,13 +189,32 @@ export default {
         status: [
           { required: true, message: "状态不能为空", trigger: "change" }
         ]
-      }
+      },
+      // 更换会员归属对话框
+      changeCompanyUserOpen: false,
+      // 更换会员归属表单
+      changeCompanyUserForm: {
+        companyUserId: null,
+        companyId: null
+      },
+      // 更换会员归属表单校验
+      changeCompanyUserRules: {
+        companyUserId: [
+          { required: true, message: "请选择销售", trigger: "change" }
+        ]
+      },
+      // 销售选项
+      companyUserOptions: []
     };
   },
   created() {
     this.getList();
   },
   methods: {
+    // 查看企微账号
+    handleQwAccount(row){
+
+    },
     /** 查询用户列表 */
     getList() {
       this.loading = true;
@@ -211,22 +264,6 @@ export default {
       this.single = selection.length!==1
       this.multiple = !selection.length
     },
-    /** 新增按钮操作 */
-    handleAdd() {
-      this.reset();
-      this.open = true;
-      this.title = "添加用户";
-    },
-    /** 修改按钮操作 */
-    handleUpdate(row) {
-      this.reset();
-      const userId = row.userId || this.ids
-      getUser(userId).then(response => {
-        this.form = response.data;
-        this.open = true;
-        this.title = "修改用户";
-      });
-    },
     /** 提交按钮 */
     submitForm() {
       this.$refs["form"].validate(valid => {
@@ -251,54 +288,103 @@ export default {
     handleDelete(row) {
       const userIds = row.userId || this.ids;
       this.$confirm('是否确认删除用户编号为"' + userIds + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return delUser(userIds);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("删除成功");
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delUser(userIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
     },
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有用户数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return exportUser(queryParams);
-        }).then(response => {
-          this.download(response.msg);
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return exportUser(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+      }).catch(function() {});
     },
-    // 获取状态类型
-    getStatusType(status) {
-      switch (status) {
-        case 1:
-          return 'success';
-        case 2:
-          return 'warning';
-        case 3:
-          return 'info';
-        default:
-          return '';
-      }
+    /** 更换会员归属按钮操作 */
+    handleChangeCompanyUser() {
+      // 获取销售列表
+      getCompanyUserList().then(response => {
+        if (response.code === 200) {
+          this.companyUserOptions = response.data;
+          console.log("列表", this.companyUserOptions);
+          this.changeCompanyUserOpen = true;
+        } else {
+          this.$message.error(response.msg || '获取销售列表失败');
+        }
+      }).catch(() => {
+        this.$message.error('获取销售列表失败');
+      });
+    },
+    /** 取消更换会员归属 */
+    cancelChangeCompanyUser() {
+      this.changeCompanyUserOpen = false;
+      this.resetCompanyUserForm();
     },
-    // 获取状态文本
-    getStatusText(status) {
-      switch (status) {
-        case 1:
-          return '正常';
-        case 2:
-          return '停止';
-        case 3:
-          return '未看';
-        default:
-          return '';
+    /** 销售选择变化 */
+    handleCompanyUserChange(value) {
+      const selectedUser = this.companyUserOptions.find(item => item.userId === value);
+      if (selectedUser) {
+        this.changeCompanyUserForm.companyId = selectedUser.companyId;
+        console.log("选择的销售信息:", selectedUser);
+        console.log("设置的companyId:", this.changeCompanyUserForm.companyId);
       }
+    },
+    /** 重置更换会员归属表单 */
+    resetCompanyUserForm() {
+      this.changeCompanyUserForm = {
+        companyUserId: null,
+        companyId: null
+      };
+      this.resetForm("changeCompanyUserForm");
+    },
+    /** 提交更换会员归属 */
+    submitChangeCompanyUserForm() {
+      this.$refs["changeCompanyUserForm"].validate(valid => {
+        if (valid) {
+          // 调用更换会员归属接口
+          console.log("销售", this.changeCompanyUserForm);
+          console.log("用户", this.ids);
+
+          // 检查companyId是否已设置
+          if (!this.changeCompanyUserForm.companyId) {
+            this.$message.error('请选择销售');
+            return;
+          }
+
+          // 确保userIds是List<Long>类型
+          // const userIds = this.ids.map(id => Number(id));
+          console.log({
+            userIds:  this.ids,
+            companyUserId: this.changeCompanyUserForm.companyUserId,
+            companyId: this.changeCompanyUserForm.companyId
+          })
+          changeCompanyUser(this.ids, {
+            companyUserId: this.changeCompanyUserForm.companyUserId,
+            companyId: this.changeCompanyUserForm.companyId
+          }).then(response => {
+            if (response.code === 200) {
+              this.msgSuccess("操作成功");
+              this.changeCompanyUserOpen = false;
+              this.getList();
+            } else {
+              this.$message.error(response.msg || '操作失败');
+            }
+          }).catch(() => {
+            this.$message.error('操作失败');
+          });
+        }
+      });
     }
   }
 };

+ 1648 - 106
src/views/index.vue

@@ -1,130 +1,1672 @@
 <template>
-   <div class="dashboard-container">
-      <div class="dashboard-editor-container">
-        <!-- <panel-group-t /> -->
-        <panel-group />
-        <div class="divBox">
-            <el-card :bordered="false" dis-hover>
-              <div slot="header">
-                <div class="acea-row row-middle">
-                  <el-avatar
-                    icon="el-icon-s-operation"
-                    size="small"
-                    style="color: #1890ff; background: #e6f7ff; font-size: 13px"
-                  />
-                  <span class="ivu-pl-8">统计</span>
-                </div>
-              </div>
-              <el-row :gutter="24">
-                <el-col
-                  :xl="12"
-                  :lg="12"
-                  :md="24"
-                  :sm="24"
-                  :xs="24"
-                  class="ivu-mb mb10 dashboard-console-visit"
-                >
-                  <span class="ivu-pl-8">本月订单数</span>
-                  <store-order-chart />
-                </el-col>
-                <el-col
-                  :xl="12"
-                  :lg="12"
-                  :md="24"
-                  :sm="24"
-                  :xs="24"
-                  class="ivu-mb mb10 dashboard-console-visit"
-                >
-                  <span class="ivu-pl-8">本月收款数</span>
-                  <store-payment-chart />
-                </el-col>
-              </el-row>
-            </el-card>
+  <div class="statistics-dashboard">
+    <!-- 数据概览 (Data Overview) -->
+    <el-card class="overview-section" shadow="never">
+      <div slot="header" class="header">
+        <span>数据概览</span>
+      </div>
+
+      <el-row :gutter="20">
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-user-solid"></i>
+              分公司数量
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="dealderCount" :duration="3600" class="card-panel-num" /></div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-user"></i>
+              销售数量
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="groupMgrCount" :duration="3600" class="card-panel-num" />
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-shopping-cart-full"></i>
+              会员数量
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="memberCount" :duration="3600" class="card-panel-num" />
+              <span class="highlight-today-add">+{{todayIncreaseUserNum}}</span>
+            </div>
+            <div class="card-badge">
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-shopping-cart-full"></i>
+              企微数量
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="memberCount" :duration="3600" class="card-panel-num" /></div>
+            <div class="card-badge">
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-money"></i>
+              可用余额
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="balance" :duration="3600" class="card-panel-num" />
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <span>今日消耗</span>
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="todayComsumption" :duration="3600" class="card-panel-num" />
+            </div>
+            <div class="card-sub">
+              <span>昨日消耗(元)</span>
+              <span class="sub-value">
+                <count-to :start-val="0" :end-val="yesterdayComsumption" :duration="3600" class="card-panel-num" />
+              </span>
+            </div>
+            <el-progress :percentage="percentage" :show-text="false" color="#409EFF"></el-progress>
+            <div class="card-desc">{{remainMessage}}</div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <span class="cdn-label">CDN</span>
+              今日
+            </div>
+            <div class="card-value highlight">{{formatBytes(this.todayTraffic)}}
+            </div>
+            <div class="card-sub">
+              <span>本月</span>
+              <span class="sub-value">{{formatBytes(this.thisMonthTraffic)}}</span>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-message"></i>
+              短信剩余条数
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="smsRemainCount" :duration="3600" class="card-panel-num" />
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              平台今日看课人数
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="todayWatchUserCount" :duration="3600" class="card-panel-num" />
+            </div>
+            <div class="card-sub">
+              <span>配额上限</span>
+              <span class="sub-value">
+                <count-to :start-val="0" :end-val="todayWatchUserCount" :duration="3600" class="card-panel-num" />/<count-to :start-val="0" :end-val="versionLimit" :duration="3600" class="card-panel-num" /></span>
+            </div>
+            <el-progress :percentage="todayWatchUserCount/versionLimit" :show-text="false" color="#409EFF"></el-progress>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-shopping-cart-full"></i>
+              订单总数
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="orderTotalNum" :duration="3600" class="card-panel-num" />
+              <span class="highlight-today-add">+{{todayOrderNum}}</span>
+            </div>
+            <div class="card-badge">
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-shopping-cart-full"></i>
+              收款总数
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="recvTotalNum" :duration="3600" class="card-panel-num" />
+              <span class="highlight-today-add">+{{recvTodayNum}}</span>
+            </div>
+            <div class="card-badge">
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-shopping-cart-full"></i>
+              商品总数
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="goodsTotalNum" :duration="3600" class="card-panel-num" />
+              <span class="highlight-today-add">+{{todayGoodsNum}}</span>
+            </div>
+            <div class="card-badge">
+            </div>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+    <!-- 分析概览 (Analysis Overview) -->
+    <div class="analysis-section" shadow="never">
+      <div slot="header" class="header">
+        <span>分析概览</span>
+        <div class="tab-group">
+          <el-radio-group v-model="queryTime" size="medium" @change="handleAnalysis">
+            <el-radio-button label="今日"></el-radio-button>
+            <el-radio-button label="昨日"></el-radio-button>
+            <el-radio-button label="本周"></el-radio-button>
+            <el-radio-button label="本月"></el-radio-button>
+            <el-radio-button label="上月"></el-radio-button>
+          </el-radio-group>
+        </div>
+
+        <div class="action-group">
+          <el-radio-group v-model="userTypeText" @change="handleUserType">
+            <el-radio-button label="个微"></el-radio-button>
+            <el-radio-button label="企微"></el-radio-button>
+          </el-radio-group>
+
+          <el-dropdown @command="handleAutoRefresh" trigger="click">
+            <el-button size="small" plain>
+              自动刷新
+              <i class="el-icon-arrow-down el-icon--right"></i>
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item :command="0" :class="{ 'is-active': !autoRefreshInterval }">关闭</el-dropdown-item>
+              <el-dropdown-item :command="5" :class="{ 'is-active': autoRefreshInterval === 5 }">5分钟</el-dropdown-item>
+              <el-dropdown-item :command="10" :class="{ 'is-active': autoRefreshInterval === 10 }">10分钟</el-dropdown-item>
+              <el-dropdown-item :command="15" :class="{ 'is-active': autoRefreshInterval === 15 }">15分钟</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+          <el-button size="small" plain icon="el-icon-refresh" type="primary" @click="manualRefresh">手动刷新</el-button>
+          <!--          <el-button size="small" type="primary" @click="refresh">刷新</el-button>-->
         </div>
       </div>
-  </div>
+    </div>
+    <div>
+      <el-row :gutter="20">
+        <el-col :span="12" style="position: relative">
+          <div class="analysis-card-check" :class="selectedDiv===0?'analysis-card-check-selected color':''" @click="handleToggleDiv(0)">
+            <div class="analysis-card">
+              <div class="card-icon"><i class="el-icon-monitor"></i></div>
+              <div class="card-content">
+                <div class="card-row">
+                  <span>观看人数</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="watchUserCount" :duration="3600" class="card-panel-num" />
+                  </span>
+                </div>
+                <div class="card-row">
+                  <span>完播人数</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="completedUserCount" :duration="3600" class="card-panel-num" />
+                  </span>
+                </div>
+                <div class="card-row">
+                  <span>完播率</span>
+                  <span class="highlight">{{completedRate}}%</span>
+                </div>
+              </div>
+            </div>
+            <div class="analysis-card">
+              <div class="card-icon"><i class="el-icon-video-play"></i></div>
+              <div class="card-content">
+                <div class="card-row">
+                  <span>观看次数</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="watchCount" :duration="3600" class="card-panel-num" /></span>
+                </div>
+                <div class="card-row">
+                  <span>完播次数</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="completedCount" :duration="3600" class="card-panel-num" />
+                  </span>
+                </div>
+                <div class="card-row">
+                  <span>视频完播率</span>
+                  <span class="highlight">{{watchRate}}%</span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="6" style="position: relative">
+          <div class="analysis-card-check" :class="selectedDiv===1?'analysis-card-check-selected color':''"  @click="handleToggleDiv(1)">
+            <div class="analysis-card">
+              <div class="card-icon"><i class="el-icon-headset"></i></div>
+              <div class="card-content">
+                <div class="card-row">
+                  <span>答题人数</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="answerMemberCount" :duration="3600" class="card-panel-num" />
+                  </span>
+                </div>
+                <div class="card-row">
+                  <span>正确人数</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="correctUserCount" :duration="3600" class="card-panel-num" />
+                    </span>
+                </div>
+                <div class="card-row">
+                  <span>正确率</span>
+                  <span class="highlight">{{correctRate}}%</span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="6" style="position: relative">
+          <div class="analysis-card-check" :class="selectedDiv===2?'analysis-card-check-selected color':''"  @click="handleToggleDiv(2)">
+            <div class="analysis-card">
+              <div class="card-icon"><i class="el-icon-present"></i></div>
+              <div class="card-content">
+                <div class="card-row">
+                  <span>答题红包个数</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="rewardCount" :duration="3600" class="card-panel-num" />
+                    </span>
+                </div>
+                <div class="card-row">
+                  <span>答题红包金额(元)</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="rewardMoney" :duration="3600" class="card-panel-num" /></span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+
+    <!-- 图表区域 (Charts Area) -->
+    <transition name="fade">
+      <el-row :gutter="20" class="charts-section" v-show="selectedDiv===0">
+        <el-col :span="12">
+          <el-card shadow="never">
+            <div slot="header" class="chart-header">
+              <span>会员观看、完播人数趋势图</span>
+              <div class="legend">
+                <div class="legend-item">
+                  <span class="dot viewer-dot"></span>
+                  <span>观看人数</span>
+                </div>
+                <div class="legend-item">
+                  <span class="dot complete-dot"></span>
+                  <span>完播人数</span>
+                </div>
+              </div>
+              <el-button size="small" plain class="view-more">平台每日统计 <i class="el-icon-arrow-right"></i></el-button>
+            </div>
+            <div ref="viewerChart" class="chart-container"></div>
+          </el-card>
+        </el-col>
 
+        <el-col :span="12">
+          <el-card shadow="never">
+            <div slot="header" class="chart-header">
+              <span>经销商会员观看TOP10</span>
+              <div class="legend">
+                <el-radio-group v-model="viewerType" size="small" @change="handleDealerChartData">
+                  <el-radio-button label="0">按观看人数</el-radio-button>
+                  <el-radio-button label="1">按完播人数</el-radio-button>
+                </el-radio-group>
+              </div>
+              <el-button size="small" plain class="view-more">经销商统计 <i class="el-icon-arrow-right"></i></el-button>
+            </div>
+            <div ref="dealerChart" class="chart-container"></div>
+          </el-card>
+        </el-col>
+      </el-row>
+    </transition>
+    <transition name="fade">
+      <el-row :gutter="20" class="charts-section" v-show="selectedDiv===1">
+        <el-card shadow="never">
+          <div slot="header" class="chart-header">
+            <span>课程观看TOP10</span>
+            <div class="legend">
+              <el-radio-group v-model="viewerType" size="small" @change="handleCourseWatchChart">
+                <el-radio-button label="0">按观看人数</el-radio-button>
+                <el-radio-button label="1">按完播人数</el-radio-button>
+                <el-radio-button label="2">按答题人数</el-radio-button>
+                <el-radio-button label="3">按正确人数</el-radio-button>
+              </el-radio-group>
+            </div>
+            <div class="legend">
+              <el-radio-group v-model="delerSort" @change="handleCourseWatchChart">
+                <el-radio label="DESC">前10名</el-radio>
+                <el-radio label="ASC">倒数10名</el-radio>
+              </el-radio-group>
+            </div>
+            <div class="legend">
+              <div class="legend-item">
+                <span class="dot viewer-dot"></span>
+                <span>观看人数</span>
+              </div>
+              <div class="legend-item">
+                <span class="dot complete-dot"></span>
+                <span>完播人数</span>
+              </div>
+              <div class="legend-item">
+                <span class="dot" style="background-color: #E6A23C"></span>
+                <span>答题人数</span>
+              </div>
+              <div class="legend-item">
+                <span class="dot" style="background-color: #F56C6C"></span>
+                <span>正确人数</span>
+              </div>
+            </div>
+            <el-button size="small" plain class="view-more">经销商统计 <i class="el-icon-arrow-right"></i></el-button>
+          </div>
+          <div ref="courseWatchChart" class="chart-container"></div>
+        </el-card>
+      </el-row>
+    </transition>
+
+    <transition name="fade">
+      <el-row :gutter="20" class="charts-section" v-show="selectedDiv===2">
+        <el-col :span="12">
+          <el-card shadow="never">
+            <div slot="header" class="chart-header">
+              <span>答题红包金额TOP10</span>
+              <div class="legend">
+                <el-radio-group v-model="dataType" size="small" @change="handleAnswerRedPackViewerChart">
+                  <el-radio-button label="0">按经销商排行</el-radio-button>
+                  <el-radio-button label="1">按课程排行</el-radio-button>
+                </el-radio-group>
+              </div>
+              <el-button size="small" plain class="view-more">红包记录 <i class="el-icon-arrow-right"></i></el-button>
+            </div>
+            <div ref="answerRedPackViewerChart" class="chart-container"></div>
+          </el-card>
+        </el-col>
+        <el-col :span="12">
+          <el-card shadow="never">
+            <div slot="header" class="chart-header">
+              <span>答题红包金额趋势图</span>
+              <div class="legend">
+                <div class="legend-item">
+                  <span class="dot viewer-dot"></span>
+                  <span>答题红包金额</span>
+                </div>
+              </div>
+              <el-button size="small" plain class="view-more">红包记录 <i class="el-icon-arrow-right"></i></el-button>
+            </div>
+            <div ref="answerRedPackMoneyViewerChart" class="chart-container"></div>
+          </el-card>
+        </el-col>
+      </el-row>
+    </transition>
+    <el-row :gutter="20" class="charts-section">
+      <el-col :span="12">
+        <el-card shadow="never">
+          <div slot="header" class="chart-header">
+            <span>本月订单数</span>
+            <div class="legend">
+              <div class="legend-item">
+                <span class="dot viewer-dot"></span>
+                <span>订单数</span>
+              </div>
+              <div class="legend-item">
+                <span class="dot complete-dot"></span>
+                <span>订单金额</span>
+              </div>
+            </div>
+          </div>
+          <div ref="viewerOrderChart" class="chart-container"></div>
+        </el-card>
+      </el-col>
+      <el-col :span="12">
+        <el-card shadow="never">
+          <div slot="header" class="chart-header">
+            <span>本月收款数</span>
+            <div class="legend">
+              <div class="legend-item">
+                <span class="dot viewer-dot"></span>
+                <span>收款数</span>
+              </div>
+              <div class="legend-item">
+                <span class="dot complete-dot"></span>
+                <span>收款金额</span>
+              </div>
+            </div>
+          </div>
+          <div ref="viewerReceiveChart" class="chart-container"></div>
+        </el-card>
+      </el-col>
+    </el-row>
+    <br/>
+  </div>
 </template>
 
 <script>
+import * as echarts from 'echarts'
+import CountTo from "vue-count-to";
+import {
+  analysisPreview,
+  authorizationInfo,
+  dealerAggregated, deaMemberTopTen, rechargeComsumption, rewardMoneyTopTen, rewardMoneyTrend,
+  smsBalance, thisMonthOrderCount, thisMonthRecvCount, trafficLog,
+  watchCourseTopTen, watchEndPlayTrend
+} from "@/api/statistics/statistics";
+import dayjs from 'dayjs';
 
-import PanelGroup from "./dashboard/PanelGroup";
-import PanelGroupT from "./dashboard/PanelGroupT";
-import StoreOrderChart from "./dashboard/StoreOrderChart";
-import StorePaymentChart from "./dashboard/StorePaymentChart";
-// import { count } from "@/api/visits";
-import OrderCount from "./dashboard/OrderCount";
 
+const viewCharOption = {
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
+    }
+  },
+  grid: {
+    left: '3%',
+    right: '4%',
+    bottom: '3%',
+    containLabel: true
+  },
+  xAxis: {
+    type: 'category',
+    data: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23']
+  },
+  yAxis: {
+    type: 'value'
+  },
+  series: [
+    {
+      name: '观看人数',
+      type: 'bar',
+      data: [],
+      itemStyle: {
+        color: '#409EFF'
+      }
+    },
+    {
+      name: '完播人数',
+      type: 'bar',
+      data: [],
+      itemStyle: {
+        color: '#67C23A'
+      }
+    }
+  ]
+}
 
-export default {
-  name: 'Index',
-  components: {
-    PanelGroup,
-    PanelGroupT,
-    StoreOrderChart,
-    StorePaymentChart,
-    OrderCount,
-  },
-  
-  data() {
-    return {
-      
+const thisMonthOrderCountOption = {
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
     }
   },
-   created() {
-      
+  grid: {
+    left: '3%',
+    right: '4%',
+    bottom: '3%',
+    containLabel: true
   },
-  methods: {
-   
-  }
+  xAxis: {
+    type: 'category',
+    data: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23']
+  },
+  yAxis: {
+    type: 'value'
+  },
+  series: [
+    {
+      name: '订单数',
+      type: 'line',
+      data: [],
+      itemStyle: {
+        color: '#409EFF'
+      }
+    },
+    {
+      name: '订单金额',
+      type: 'line',
+      data: [],
+      itemStyle: {
+        color: '#67C23A'
+      }
+    }
+  ]
 }
-</script>
 
+const thisMonthRecvCountOption = {
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
+    }
+  },
+  grid: {
+    left: '3%',
+    right: '4%',
+    bottom: '3%',
+    containLabel: true
+  },
+  xAxis: {
+    type: 'category',
+    data: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23']
+  },
+  yAxis: {
+    type: 'value'
+  },
+  series: [
+    {
+      name: '收款数',
+      type: 'line',
+      data: [],
+      itemStyle: {
+        color: '#409EFF'
+      }
+    },
+    {
+      name: '收款金额',
+      type: 'line',
+      data: [],
+      itemStyle: {
+        color: '#67C23A'
+      }
+    }
+  ]
+}
+const dealerOption = {
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
+    }
+  },
+  grid: {
+    left: '3%',
+    right: '4%',
+    bottom: '3%',
+    containLabel: true
+  },
+  xAxis: {
+    type: 'value'
+  },
+  yAxis: {
+    type: 'category',
+    data: []
+  },
+  series: [
+    {
+      name: '观看人数',
+      type: 'bar',
+      data: [],
+      itemStyle: {
+        color: '#409EFF'
+      }
+    }
+  ]
+}
 
-<style rel="stylesheet/scss" lang="scss" scoped>
-  .dashboard-editor-container {
-    padding: 18px 22px 22px 22px;
-    background-color: rgb(240, 242, 245);
-  
-    .chart-wrapper {
-      background: #fff;
-      padding: 16px 16px 0;
-      margin-bottom: 32px;
+const courseWatchOption = {
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
     }
-  }
-  
-  .acea-row {
-    ::v-deep.el-avatar--small {
-      width: 22px;
-      height: 22px;
-      line-height: 22px;
+  },
+  grid: {
+    left: '3%',
+    right: '4%',
+    bottom: '8%',
+    top: '3%',
+    containLabel: true
+  },
+  xAxis: {
+    type: 'category',
+    data: [],
+    axisLabel: {
+      interval: 0,
+      rotate: 30,
+      fontSize: 10,
+      width: 100,
+      overflow: 'truncate'
     }
-  }
-  .checkTime {
-    ::v-deep.el-radio__input {
-      display: none;
+  },
+  yAxis: {
+    type: 'value',
+    splitLine: {
+      lineStyle: {
+        type: 'dashed'
+      }
     }
-  }
-  .ivu-pl-8 {
-    margin-left: 8px;
-    font-size: 14px;
-  }
-  .divBox {
-    // padding: 0 20px !important;
-  }
-  .dashboard-console-visit {
-    ::v-deep.el-card__header {
-      padding: 14px 20px !important;
+  },
+  series: [
+    {
+      name: '观看人数',
+      type: 'bar',
+      data: [],
+      itemStyle: {
+        color: '#409EFF'
+      }
+    },
+    {
+      name: '完播人数',
+      type: 'bar',
+      data: [],
+      itemStyle: {
+        color: '#67C23A'
+      }
+    },
+    {
+      name: '答题人数',
+      type: 'bar',
+      data: [],
+      itemStyle: {
+        color: '#E6A23C'
+      }
+    },
+    {
+      name: '正确人数',
+      type: 'bar',
+      data: [],
+      itemStyle: {
+        color: '#F56C6C'
+      }
     }
-    ul {
-      li {
-        list-style-type: none;
-        margin-top: 12px;
+  ]
+}
+
+const lineChartOption = {
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'cross' // 改为 'cross' 更适合折线图
+    }
+  },
+  grid: {
+    left: '3%',
+    right: '4%',
+    bottom: '8%', // 如果x轴标签旋转,可能需要更大的 bottom
+    top: '5%',    // 增加一点顶部空间给可能的 Y 轴名称
+    containLabel: true
+  },
+  xAxis: {
+    type: 'time', // X轴类型改为 'time'
+    // data: [], // 时间轴不需要单独设置 data,数据在 series 中提供
+    axisLabel: {
+      // interval: 0, // 时间轴通常自动处理间隔,可以先移除或注释掉
+      rotate: 30,   // 保留旋转,如果标签可能重叠
+      fontSize: 10,
+      // width: 100, // width 和 overflow 对于时间轴可能行为不同,按需调整
+      // overflow: 'truncate',
+      formatter: null // ECharts 会自动格式化时间,如需特定格式可用 function 或字符串模板
+    }
+  },
+  yAxis: {
+    type: 'value',
+    name: '金额 (元)', // 添加 Y 轴名称
+    nameLocation: 'end', // 名称位置
+    nameTextStyle: {
+      align: 'right',
+      padding: [0, 10, 0, 0] // 调整名称与轴线的距离
+    },
+    splitLine: {
+      lineStyle: {
+        type: 'dashed'
+      }
+    },
+    axisLabel: {
+      formatter: '{value} 元' // 可选:给 Y 轴刻度添加单位
+    }
+  },
+  series: [
+    {
+      name: '答题红包金额',
+      type: 'line', // 系列类型改为 'line'
+      data: [
+      ],
+      itemStyle: { // 控制数据点(标记)的样式
+        color: '#409EFF'
+      },
+      lineStyle: { // 控制线的样式
+        color: '#409EFF'
+      },
+      smooth: false, // 是否平滑曲线,可设为 true
+      symbol: 'circle', // 数据点标记形状,'emptyCircle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none'
+      symbolSize: 4   // 数据点标记大小
+    }
+  ]
+};
+
+
+const redPackageOption = {
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
+    }
+  },
+  grid: {
+    left: '3%',
+    right: '4%',
+    bottom: '8%',
+    top: '3%',
+    containLabel: true
+  },
+  xAxis: {
+    type: 'category',
+    data: [],
+    axisLabel: {
+      interval: 0,
+      rotate: 30,
+      fontSize: 10,
+      width: 100,
+      overflow: 'truncate'
+    }
+  },
+  yAxis: {
+    type: 'value',
+    splitLine: {
+      lineStyle: {
+        type: 'dashed'
       }
     }
+  },
+  series: [
+    {
+      name: '答题红包金额',
+      type: 'bar',
+      data: [],
+      itemStyle: {
+        color: '#409EFF'
+      }
+    }
+  ]
+}
+export default {
+  name: 'StatisticsDashboard',
+  components: {CountTo},
+  data() {
+    return {
+      percentage: 0,
+      // 预测message
+      remainMessage: '',
+      // 当天使用流量
+      todayTraffic: 0,
+      // 当月使用流量
+      thisMonthTraffic: 0,
+      dataType: '0',
+      delerSort: 'DESC',
+      smsRemainCount: 0,
+      viewerType: '0',
+      viewerChart: null,
+      userTypeText: '个微',
+      userType: 1,
+      dealerChart: null,
+      // 分公司数量
+      dealderCount: 0,
+      // 销售数量
+      groupMgrCount: 0,
+      // 会员总数量
+      memberCount: 0,
+      // 企微数量
+      qwMemberNum: 0,
+      // 正常会员数量
+      normalNum: 0,
+      // 黑名单会员数量
+      blackNum: 0,
+      // 观看人数
+      watchUserCount: 0,
+      // 完播人数
+      completedUserCount: 0,
+      // 完播率
+      completedRate: 0,
+      // 观看次数
+      watchCount:0,
+      // 完播次数
+      completedCount: 0,
+      // 视频完播率
+      watchRate: 0,
+      // 答题人数
+      answerMemberCount: 0,
+      // 正确人数
+      correctUserCount: 0,
+      correctRate: 0.0,
+      // 答题红包个数
+      rewardCount: 0,
+      // 答题红包金额
+      rewardMoney: 0.0,
+      queryTime: '今日',
+      todayWatchUserCount: 0,
+      versionLimit: 0,
+      /// 选中的分析概览
+      selectedDiv: 0,
+      filterType: 0,
+      answerRedPackViewerChart: null,
+      answerRedPackMoneyViewerChart: null,
+      todayComsumption: 0,
+      yesterdayComsumption: 0,
+      balance: 0,
+      autoRefreshInterval: null,
+      // 今日新增用户数
+      todayIncreaseUserNum: 0,
+      // 订单总数
+      orderTotalNum: 0,
+      // 今日新增订单数
+      todayOrderNum: 0,
+      // 收款总数
+      recvTotalNum: 0,
+      // 今日收款总数
+      recvTodayNum: 0,
+      // 商品总数
+      goodsTotalNum: 0,
+      // 今日商品总数
+      todayGoodsNum: 0
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initViewerChart()
+      this.initDealerChart()
+      this.initCourseWatchChart();
+      this.initAnswerRedPackViewerChart();
+      this.initAnswerRedPackMoneyViewerChart();
+      this.initThisMonthOrderChart();
+      this.initThisMonthRecvChart()
+
+      // 监听窗口大小变化,重新渲染图表
+      window.addEventListener('resize', () => {
+        this.viewerChart && this.viewerChart.resize()
+        this.dealerChart && this.dealerChart.resize()
+      })
+    })
+  },
+  created() {
+    this.refresh();
+  },
+  methods: {
+    handleUserType(){
+      if(this.userTypeText === '个微'){
+        this.userType = 1
+      }else{
+        this.userType = 2
+      }
+
+      this.refresh()
+    },
+    /**
+     * 计算余额预计可持续的天数
+     * @param {number} balance - 当前账户余额
+     * @param {number} todayConsumption - 今日消耗金额
+     * @param {number} yesterdayConsumption - 昨日消耗金额
+     * @return {Object} 包含天数和进度百分比的对象
+     */
+    calculateRemainingDays(balance, todayConsumption, yesterdayConsumption) {
+      // 如果今日和昨日消耗都为0,则无法预测(避免除以0)
+      if (todayConsumption === 0 && yesterdayConsumption === 0) {
+        return {
+          days: Infinity,
+          percentage: 0,
+          message: '暂无消耗数据'
+        };
+      }
+
+      // 计算每日平均消耗量
+      const avgDailyConsumption = (todayConsumption + yesterdayConsumption) / 2;
+
+      // 如果平均消耗为0,则无法预测
+      if (avgDailyConsumption === 0) {
+        return {
+          days: Infinity,
+          percentage: 0,
+          message: '暂无消耗数据'
+        };
+      }
+
+      // 计算剩余天数(向下取整)
+      const remainingDays = Math.floor(balance / avgDailyConsumption);
+
+      // 计算进度条百分比,最大为100
+      // 这里假设100天是满值,可以根据需要调整
+      const maxDays = 100;
+      const percentage = Math.min(100, Math.max(0, Math.round((remainingDays / maxDays) * 100)));
+
+      let message = '';
+      if (remainingDays > 365) {
+        message = '预测余额充足';
+      } else {
+        message = `预测不足${remainingDays}天`;
+      }
+
+      return {
+        days: remainingDays,
+        percentage: 100 - percentage,
+        message: message
+      };
+    },
+    /**
+     * 将字节数转换为合适的单位表示(Byte、KB、MB、GB、TB)
+     * @param {number} bytes - 字节数
+     * @param {number} [decimals=2] - 小数点后保留的位数
+     * @returns {string} 格式化后的字符串,包含数值和单位
+     */
+    formatBytes(bytes, decimals = 2) {
+      if (bytes === 0) return '0 Byte';
+
+      const k = 1024;
+      const sizes = ['Byte', 'KB', 'MB', 'GB', 'TB'];
+
+      // 计算合适的单位级别
+      const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+      // 转换为对应单位的值
+      const value = bytes / Math.pow(k, i);
+
+      // 格式化为指定小数位的字符串
+      return parseFloat(value.toFixed(decimals)) + ' ' + sizes[Math.min(i, sizes.length - 1)];
+    },
+    // 手动刷新
+    manualRefresh() {
+      this.refresh();
+    },
+    // 处理自动刷新选项
+    handleAutoRefresh(command) {
+      // 清除之前的定时器
+      if (this.timer) {
+        clearInterval(this.timer);
+        this.timer = null;
+      }
+
+      // 设置新的刷新间隔
+      this.autoRefreshInterval = parseInt(command);
+
+      // 如果间隔大于0,设置新的定时器
+      if (this.autoRefreshInterval > 0) {
+        this.timer = setInterval(() => {
+          this.refresh();
+        }, this.autoRefreshInterval * 60 * 1000); // 转换为毫秒
+
+        this.$message.success(`已设置${this.autoRefreshInterval}分钟自动刷新`);
+      } else {
+        this.$message.info('已关闭自动刷新');
+      }
+    },
+    refresh() {
+      rechargeComsumption().then(res=>{
+        if(res.code === 200){
+          this.balance = res.data.balance;
+          this.todayComsumption = res.data.todayComsumption;
+          this.yesterdayComsumption = res.data.yesterdayComsumption;
+          let calculateRemainingDays1 = this.calculateRemainingDays(this.balance,this.todayComsumption,this.yesterdayComsumption);
+          this.percentage = calculateRemainingDays1.percentage;
+          this.remainMessage = calculateRemainingDays1.message;
+        }
+      });
+
+      trafficLog().then(res=>{
+        if(res.code === 200) {
+          this.todayTraffic = res.data.today;
+          this.thisMonthTraffic = res.data.thisMonth;
+        }
+      })
+
+      dealerAggregated().then(res=>{
+        if(res.code === 200){
+          this.dealderCount = res.data.dealderCount??0;
+          this.groupMgrCount = res.data.groupMgrCount??0;
+          this.memberCount = res.data.memberCount??0;
+          this.qwMemberNum = res.data.qwMemberNum??0;
+          this.normalNum = res.data.normalNum??0;
+          this.blackNum = res.data.blackNum??0;
+          this.todayIncreaseUserNum = res.data.todayIncreaseUserNum??0;
+          this.orderTotalNum = res.data.orderTotalNum??0;
+          this.todayOrderNum = res.data.todayOrderNum??0;
+          this.recvTotalNum = res.data.recvTotalNum??0;
+          this.recvTodayNum = res.data.recvTodayNum??0;
+          this.goodsTotalNum = res.data.goodsTotalNum??0;
+          this.todayGoodsNum = res.data.todayGoodsNum??0;
+        }
+      })
+      let param = this.getParam();
+
+      // 获取当前日期时间
+      const today = dayjs();
+      param.startTime = this.formatDate(today);
+      param.endTime = this.formatDate(today);
+      analysisPreview(param).then(res=>{
+        if(res.code === 200){
+          this.watchUserCount = res.data.watchUserCount;
+          this.completedUserCount = res.data.completedUserCount;
+          this.completedRate = res.data.completedRate;
+          this.watchCount = res.data.watchCount;
+          this.completedCount = res.data.completedCount;
+          this.answerMemberCount = res.data.answerMemberCount;
+          this.correctUserCount = res.data.correctUserCount;
+          this.correctRate = res.data.correctRate;
+          this.rewardCount = res.data.rewardCount;
+          this.rewardMoney = res.data.rewardMoney;
+          this.watchRate = res.data.watchRate;
+        }
+      })
+      smsBalance().then(res=>{
+        if(res.code === 200){
+          if(res.data == null) {
+            this.smsRemainCount = 0;
+          } else {
+            this.smsRemainCount = res.data;
+          }
+        }
+      })
+      authorizationInfo().then(res=>{
+        if(res.code === 200){
+          this.todayWatchUserCount = res.data.todayWatchUserCount;
+          this.versionLimit = res.data.versionLimit;
+        }
+      })
+
+      this.handleCourseWatchChart()
+      this.handleViewChartData()
+
+      // 经销商会员观看TOP10
+      this.handleDealerChartData()
+
+      this.handleAnswerRedPackViewerChart()
+
+      this.handleAnswerRedPackMoneyViewerChart()
+
+      this.handleThisMonthRecvCount();
+      this.handleThisMonthOrderCount();
+
+    },
+    /**
+     * 将数字添加千位分隔符
+     * @param {number|string} num - 需要格式化的数字
+     * @return {string} 添加千位分隔符后的字符串
+     */
+    formatNumberWithCommas(num) {
+      if (num === null || num === undefined || isNaN(Number(num))) {
+        return '0';
+      }
+
+      const numStr = String(num);
+
+      // 处理负数
+      const isNegative = numStr.startsWith('-');
+      const absNumStr = isNegative ? numStr.slice(1) : numStr;
+
+      // 分离整数部分和小数部分
+      const parts = absNumStr.split('.');
+      const integerPart = parts[0];
+      const decimalPart = parts.length > 1 ? '.' + parts[1] : '';
+
+      const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+
+      return (isNegative ? '-' : '') + formattedInteger + decimalPart;
+    },
+    handleToggleDiv(selected){
+      this.selectedDiv = selected;
+
+      if (selected === 1) {
+        this.$nextTick(() => {
+          if (this.courseWatchChart) {
+            this.courseWatchChart.resize();
+          } else {
+          }
+        });
+      }
+      else if (selected === 0) {
+        this.$nextTick(() => {
+          if (this.viewerChart) this.viewerChart.resize();
+          if (this.dealerChart) this.dealerChart.resize();
+        });
+      } else if (selected === 2) {
+        this.$nextTick(() => {
+          if (this.answerRedPackViewerChart) this.answerRedPackViewerChart.resize();
+          if (this.answerRedPackMoneyViewerChart) this.answerRedPackMoneyViewerChart.resize();
+        });
+      }
+      if(this.selectedDiv === 0){
+        this.handleViewChartData()
+        this.handleDealerChartData()
+      } else if(this.selectedDiv === 1) {
+        this.handleCourseWatchChart()
+      } else if(this.selectedDiv === 2) {
+        this.handleAnswerRedPackViewerChart()
+        this.handleAnswerRedPackMoneyViewerChart()
+      }
+    },
+    formatDate(date) {
+      return dayjs(date).format('YYYY-MM-DD');
+    },
+
+    getParam(){
+      let param = {
+        startTime: '',
+        endTime: '',
+        userType: this.userType
+      };
+      // 获取当前日期时间
+      const today = dayjs();
+
+      let type = 0;
+      if (this.queryTime === '今日') {
+        param.startTime = this.formatDate(today);
+        param.endTime = this.formatDate(today);
+        type = 0;
+      } else if (this.queryTime === '昨日') {
+        const yesterday = today.subtract(1, 'day');
+        param.startTime = this.formatDate(yesterday);
+        param.endTime = this.formatDate(yesterday);
+        type = 1;
+      } else if (this.queryTime === '本周') {
+        param.startTime = this.formatDate(today.startOf('week'));
+        param.endTime = this.formatDate(today.endOf('week'));
+        type = 2;
+      } else if (this.queryTime === '本月') {
+        param.startTime = this.formatDate(today.startOf('month'));
+        param.endTime = this.formatDate(today.endOf('month'));
+        type = 3;
+      } else if (this.queryTime === '上月') {
+        const lastMonth = today.subtract(1, 'month');
+        param.startTime = this.formatDate(lastMonth.startOf('month'));
+        param.endTime = this.formatDate(lastMonth.endOf('month'));
+        type = 4;
+      } else {
+        console.warn(`未知的 queryTime: ${this.queryTime}, 默认使用今日`);
+        param.startTime = this.formatDate(today);
+        param.endTime = this.formatDate(today);
+      }
+      param.type = type;
+      param.sort = this.delerSort;
+      return param;
+    },
+    // 分析概览
+    handleAnalysis(e){
+
+      let param = this.getParam();
+      analysisPreview(param).then(res=>{
+        if(res.code === 200){
+          this.watchUserCount = res.data.watchUserCount;
+          this.completedUserCount = res.data.completedUserCount;
+          this.completedRate = res.data.completedRate;
+          this.watchCount = res.data.watchCount;
+          this.completedCount = res.data.completedCount;
+          this.answerMemberCount = res.data.answerMemberCount;
+          this.correctUserCount = res.data.correctUserCount;
+          this.correctRate = res.data.correctRate;
+          this.rewardCount = res.data.rewardCount;
+          this.rewardMoney = res.data.rewardMoney;
+          this.watchRate = res.data.watchRate;
+        }
+      })
+
+      if(this.selectedDiv === 0){
+        this.handleViewChartData()
+        this.handleDealerChartData()
+      } else if(this.selectedDiv === 1) {
+        this.handleCourseWatchChart()
+      } else if(this.selectedDiv === 2) {
+        this.handleAnswerRedPackViewerChart()
+        this.handleAnswerRedPackMoneyViewerChart()
+      }
+    },
+    handleAnswerRedPackViewerChart(){
+      let param = this.getParam();
+      param = {...param,statisticalType:this.viewerType,dataType: this.dataType};
+      rewardMoneyTopTen(param).then(res=>{
+        if(res.code === 200){
+          let data = res.data;
+          let companyNameList = data.map(e=>e.companyName)
+          let courseNameList = data.map(e=>e.courseName)
+          let rewardMoneyList = data.map(e=>e.rewardMoney)
+          if(this.dataType === '0'){
+            redPackageOption.xAxis.data = companyNameList;
+          }else{
+            redPackageOption.xAxis.data = courseNameList;
+          }
+          redPackageOption.series[0].data = rewardMoneyList;
+
+          this.answerRedPackViewerChart.setOption(redPackageOption)
+        }
+      })
+    },
+    handleAnswerRedPackMoneyViewerChart(){
+      let param = this.getParam();
+      param = {...param,statisticalType:this.viewerType,dataType: this.dataType};
+      rewardMoneyTrend(param).then(res=>{
+        if(res.code === 200){
+          let data = res.data;
+          let option = data.map(e=>[e.x,e.rewardMoney])
+          lineChartOption.series[0].data = option;
+
+          this.answerRedPackMoneyViewerChart.setOption(lineChartOption)
+        }
+      })
+    },
+    handleCourseWatchChart() {
+      let param = this.getParam();
+      param = {...param,statisticalType:this.viewerType};
+      watchCourseTopTen(param).then(res=>{
+        if(res.code === 200){
+          let data = res.data;
+          let watchUserCountList = data.map(e=>e.watchUserCount);
+          let completedUserCountList = data.map(e=>e.completedUserCount);
+          let answerUserCountList = data.map(e=>e.answerUserCount);
+          let correctUserCountList = data.map(e=>e.correctUserCount);
+          let courseNameList = data.map(e=>e.courseName);
+          courseWatchOption.xAxis.data = courseNameList;
+          courseWatchOption.series[0].data = watchUserCountList;
+          courseWatchOption.series[1].data = completedUserCountList;
+          courseWatchOption.series[2].data = answerUserCountList;
+          courseWatchOption.series[3].data = correctUserCountList;
+          this.courseWatchChart.setOption(courseWatchOption)
+        }
+      })
+    },
+    handleDealerChartData(){
+      let param = this.getParam();
+
+      // 经销商会员观看TOP10
+      deaMemberTopTen({...param,statisticalType: this.viewerType}).then(res=>{
+        if(res.code === 200){
+          let data = res.data;
+          let companyNameList = data.map(e=>e.companyName);
+          let watchUserList = data.map(e=>e.watchUserCount);
+          dealerOption.yAxis.data = companyNameList;
+          dealerOption.series[0].data = watchUserList;
+
+          this.dealerChart.setOption(dealerOption)
+        }
+      })
+
+    },
+    handleThisMonthOrderCount(){
+      thisMonthOrderCount().then(res=>{
+        if(res.code === 200){
+          let dates = res.dates;
+          let orderCount = res.orderCount;
+          let payPrice = res.payPrice;
+
+          thisMonthOrderCountOption.series[0].data = orderCount;
+          thisMonthOrderCountOption.series[1].data = payPrice;
+          thisMonthOrderCountOption.xAxis.data = dates;
+
+          this.thisMonthOrderChart.setOption(thisMonthOrderCountOption)
+        }
+      })
+    },
+    handleThisMonthRecvCount(){
+      thisMonthRecvCount().then(res=>{
+        if(res.code === 200){
+          let dates = res.dates;
+          let orderCount = res.orderCount;
+          let payMoney = res.payMoney;
+
+          thisMonthRecvCountOption.series[0].data = orderCount;
+          thisMonthRecvCountOption.series[1].data = payMoney;
+          thisMonthRecvCountOption.xAxis.data = dates;
+          this.thisMonthRecvChart.setOption(thisMonthRecvCountOption)
+        }
+      })
+    },
+    handleViewChartData(){
+      let param = this.getParam();
+
+      watchEndPlayTrend({...param}).then(res=>{
+        if(res.code === 200){
+          let data = res.data;
+          let watchUserCountList = data.map(e=>e.watchUserCount);
+          let completedUserCountList = data.map(e=>e.completedUserCount);
+          let xAxis = data.map(e=>e.x);
+          viewCharOption.series[0].data = watchUserCountList;
+          viewCharOption.series[1].data = completedUserCountList;
+          viewCharOption.xAxis.data = xAxis;
+
+          this.viewerChart.setOption(viewCharOption);
+        }
+      })
+
+    },
+    initThisMonthOrderChart(){
+      this.thisMonthOrderChart = echarts.init(this.$refs.viewerOrderChart)
+      this.thisMonthOrderChart.setOption(thisMonthOrderCountOption)
+    },
+    initThisMonthRecvChart(){
+      this.thisMonthRecvChart = echarts.init(this.$refs.viewerReceiveChart)
+      this.thisMonthRecvChart.setOption(thisMonthOrderCountOption)
+    },
+    initViewerChart() {
+      this.viewerChart = echarts.init(this.$refs.viewerChart)
+      this.viewerChart.setOption(viewCharOption)
+    },
+    initDealerChart() {
+      this.dealerChart = echarts.init(this.$refs.dealerChart)
+
+      this.dealerChart.setOption(dealerOption)
+    },
+    initCourseWatchChart() {
+      this.courseWatchChart = echarts.init(this.$refs.courseWatchChart)
+
+      this.courseWatchChart.setOption(courseWatchOption)
+    },
+    initAnswerRedPackViewerChart(){
+      this.answerRedPackViewerChart = echarts.init(this.$refs.answerRedPackViewerChart)
+
+      this.answerRedPackViewerChart.setOption(redPackageOption)
+    },
+    initAnswerRedPackMoneyViewerChart(){
+      this.answerRedPackMoneyViewerChart = echarts.init(this.$refs.answerRedPackMoneyViewerChart)
+
+      this.answerRedPackMoneyViewerChart.setOption(lineChartOption)
+    }
+  },
+
+  beforeDestroy() {
+    // 组件销毁时清除定时器
+    if (this.timer) {
+      clearInterval(this.timer);
+      this.timer = null;
+    }
+    // window.removeEventListener('resize', this.resizeHandler)
+    this.viewerChart && this.viewerChart.dispose()
+    this.dealerChart && this.dealerChart.dispose()
   }
-  .ivu-mb {
-    margin-bottom: 10px;
+}
+</script>
+
+<style scoped>
+.highlight-today-add{
+  color:green;font-size:17px;font-weight: normal;
+}
+.action-group .el-button + .el-button,
+.action-group .el-dropdown {
+  margin-left: 10px;
+}
+.is-active {
+  color: #409EFF;
+  font-weight: bold;
+}
+::v-deep .el-radio-button__inner:hover {
+  color: #409EFF; /* 鼠标悬浮时的文字颜色,可以根据需要调整 */
+}
+::v-deep .el-radio-button.is-active .el-radio-button__inner {
+  background-color: #409EFF; /* 选中时的背景色 */
+  border-color: #409EFF;     /* 选中时的边框色 */
+  color: #FFFFFF;           /* 选中时的文字颜色 (通常是白色) */
+  box-shadow: -1px 0 0 0 #409EFF; /* 处理按钮间的连接缝隙 */
+}
+/* 如果需要,也可以修改非选中状态下的聚焦(focus)或悬浮(hover)样式 */
+/* 例如,让非选中按钮悬浮时边框和文字也变蓝 */
+::v-deep .el-radio-button:not(.is-active) .el-radio-button__inner:hover {
+  color: #409EFF;
+  /* border-color: #b3d8ff;  Element UI 默认悬浮边框色,可以按需修改 */
+}
+/* 聚焦时的外框,如果需要的话 */
+::v-deep .el-radio-button:focus:not(.is-checked) .el-radio-button__inner {
+  /* border-color: #409EFF; */ /* Element UI 默认的 focus 颜色通常关联主题色 */
+  /* box-shadow: 0 0 2px 2px rgba(64, 158, 255, 0.2); */ /* 示例 focus 光晕 */
+}
+.statistics-dashboard {
+  padding: 20px;
+  background-color: #f5f7fa;
+}
+
+.overview-section,
+.analysis-section {
+  margin-bottom: 20px;
+  border-radius: 4px;
+}
+
+.header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  font-size: 16px;
+  font-weight: 500;
+}
+
+.data-card {
+  background-color: #fff;
+  border-radius: 4px;
+  padding: 15px;
+  height: 120px;
+  display: flex;
+  flex-direction: column;
+  position: relative;
+  transition: background-color 0.3s ease-in-out;
+}
+.data-card:hover{
+  border: 1px solid #4592ff;
+  background-color: #e7f1ff;
+}
+
+.card-title {
+  color: #606266;
+  font-size: 14px;
+  margin-bottom: 10px;
+}
+
+.card-value {
+  font-size: 24px;
+  font-weight: bold;
+  margin-top: auto;
+}
+
+.highlight {
+  color: #409EFF;
+}
+
+.card-sub {
+  display: flex;
+  justify-content: space-between;
+  font-size: 12px;
+  color: #909399;
+  margin-top: 5px;
+}
+
+.card-desc {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 5px;
+}
+
+.card-badge {
+  position: absolute;
+  top: 15px;
+  right: 15px;
+  background: #f0f9eb;
+  color: #67c23a;
+  padding: 2px 5px;
+  border-radius: 4px;
+}
+
+.cdn-label {
+  background-color: #409EFF;
+  color: white;
+  padding: 2px 5px;
+  border-radius: 4px;
+  margin-right: 5px;
+  font-size: 12px;
+}
+
+.tab-group {
+  display: flex;
+  gap: 10px;
+}
+
+.action-group {
+  display: flex;
+  gap: 10px;
+}
+
+.analysis-card {
+  border-radius: 4px;
+  padding: 20px;
+  display: flex;
+  align-items: center;
+}
+
+.card-icon {
+  width: 50px;
+  height: 50px;
+  background-color: rgba(64, 158, 255, 0.1);
+  border-radius: 8px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-size: 24px;
+  color: #409EFF;
+  margin-right: 20px;
+}
+
+.card-content {
+  display: flex;
+}
+
+.card-row {
+  display: flex;
+  justify-content: center;
+  justify-items: center;
+  flex-direction: column;
+  padding: 10px;
+  .highlight{
+    text-align: center;
+    margin-top: 1em;
+
+    font-family: BebasNeue;
+    color: #1677ff;
+    font-size: 26px;
+    line-height: 42px;
+    font-weight: 400;
+    margin-top: 8px;
   }
-  </style>
-  
+  font-size: 15px;
+  color: #000;
+
+}
+
+.charts-section {
+  margin-top: 20px;
+}
+
+.chart-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.view-more {
+  font-size: 12px;
+}
+
+.legend {
+  display: flex;
+  gap: 15px;
+}
+
+.legend-item {
+  display: flex;
+  align-items: center;
+  font-size: 12px;
+}
+
+.dot {
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  margin-right: 5px;
+}
+
+.viewer-dot {
+  background-color: #409EFF;
+}
+
+.complete-dot {
+  background-color: #67C23A;
+}
+
+.chart-container {
+  height: 350px;
+  width: 100%;
+}
+.analysis-card-check{
+  display: flex;
+  flex-direction: row;
+  border: 1px solid transparent;
+  background-color: #fff;
+  border-radius: 4px;
+}
+.analysis-card-check:hover{
+  cursor: pointer;
+}
+.analysis-card-check-selected:after{
+  content: "";
+  display: block;
+  border-width: 15px;
+  position: absolute;
+  bottom: -30px;
+  left: 50%;
+  margin-left: -32px;
+  border-style: solid dashed dashed solid;
+  border-color: #4592FF transparent transparent transparent;
+  font-size: 0;
+  line-height: 0;
+  z-index:1;
+}
+.analysis-card-check-selected:before{
+  content: "";
+  display: block;
+  border-width: 15px;
+  position: absolute;
+  bottom: -30px;
+  left: 50%;
+  margin-left: -32px;
+  border-style: solid dashed dashed solid;
+  border-color: #4592FF transparent transparent transparent;
+  font-size: 0;
+  line-height: 0;
+  z-index:1;
+}
+.analysis-card-check-selected{
+  border: 1px solid #4592FF;
+  background-color: #e7f1ff;
+}
+.color{
+  position: relative;
+  border: 1px solid #4592FF;
+  background-color: #e7f1ff;
+}
+.color:after {
+  bottom: -27px;
+  border-color: #E7F1FF transparent transparent transparent;
+}
+.legend-group{
+
+}
+</style>

+ 392 - 392
src/views/oms/orderInternal/add.vue

@@ -1,252 +1,252 @@
 <template>
   <div class="app-container">
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px" style="margin-bottom:40px">
-        <div class="bj-divider">
-          <div class="left"> 
-                <span class="default-text">三方订单基本信息</span>
-                <div class="button"></div>
-                <span class="tips"></span>
-            </div>
+    <el-form ref="form" :model="form" :rules="rules" label-width="80px" style="margin-bottom:40px">
+      <div class="bj-divider">
+        <div class="left">
+          <span class="default-text">三方订单基本信息</span>
+          <div class="button"></div>
+          <span class="tips"></span>
         </div>
-        <el-row>
-          <el-col :span="9">
-              <el-form-item  label-width="100px" label="三方订单号" prop="outerOrderNo"  style="margin-right:20px">
-                 <el-input v-model="form.outerOrderNo" placeholder="请输入三方订单号" />
-              </el-form-item>
-          </el-col>
-          <el-col :span="9">
-              <el-form-item label-width="140px" label="三方订单下单时间" prop="orderTime" >
-                <el-date-picker clearable size="small" style="width:100%"
-                  v-model="form.orderTime"
-                  type="date"
-                  value-format="yyyy-MM-dd"
-                  placeholder="请选择">
-                </el-date-picker>
-              </el-form-item>
-          </el-col>
-           <el-col :span="9">
-                <el-form-item label-width="100px" label="销售渠道" prop="channelType"  style="margin-right:20px">
-                    <el-select  style="width:100%"  @change="channelChange" v-model="form.channelType" :placeholder="storePlaceholder" clearable size="small">
-                      <el-option
-                            v-for="item in channelTypeOptions"
-                            :key="'channelType'+item.dictValue"  
-                            :label="item.dictLabel"
-                            :value="item.dictValue"/>
-                    </el-select>
-                </el-form-item>
-           </el-col>
-           <el-col :span="9">
-                 <el-form-item label-width="140px" label="选择销售店铺" prop="storeId">
-                    <el-select style="width:100%" :disabled="storeAvailable" ref="storeId" v-model="form.storeId" placeholder="请选择销售店铺" clearable size="small">
-                      <el-option
-                            v-for="item in stores"
-                            :key="'omsStore'+item.storeId"  
-                            :label="item.storeName"
-                            :value="item.storeId"/>
-                    </el-select>
-                </el-form-item>    
-           </el-col>
-           <el-col :span="18">
-                <el-form-item label-width="100px" label="商家备注" prop="channelType">
-                    <el-input type="textarea" v-model="form.orderRemark" show-word-limit maxlength="200"></el-input>
-               </el-form-item>   
-           </el-col>
-        </el-row>
+      </div>
+      <el-row>
+        <el-col :span="9">
+          <el-form-item  label-width="100px" label="三方订单号" prop="outerOrderNo"  style="margin-right:20px">
+            <el-input v-model="form.outerOrderNo" placeholder="请输入三方订单号" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="9">
+          <el-form-item label-width="140px" label="三方订单下单时间" prop="orderTime" >
+            <el-date-picker clearable size="small" style="width:100%"
+                            v-model="form.orderTime"
+                            type="date"
+                            value-format="yyyy-MM-dd"
+                            placeholder="请选择">
+            </el-date-picker>
+          </el-form-item>
+        </el-col>
+        <el-col :span="9">
+          <el-form-item label-width="100px" label="销售渠道" prop="channelType"  style="margin-right:20px">
+            <el-select  style="width:100%"  @change="channelChange" v-model="form.channelType" :placeholder="storePlaceholder" clearable size="small">
+              <el-option
+                v-for="item in channelTypeOptions"
+                :key="'channelType'+item.dictValue"
+                :label="item.dictLabel"
+                :value="item.dictValue"/>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="9">
+          <el-form-item label-width="140px" label="选择销售店铺" prop="storeId">
+            <el-select style="width:100%" :disabled="storeAvailable" ref="storeId" v-model="form.storeId" placeholder="请选择销售店铺" clearable size="small">
+              <el-option
+                v-for="item in stores"
+                :key="'omsStore'+item.storeId"
+                :label="item.storeName"
+                :value="item.storeId"/>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="18">
+          <el-form-item label-width="100px" label="商家备注" prop="channelType">
+            <el-input type="textarea" v-model="form.orderRemark" show-word-limit maxlength="200"></el-input>
+          </el-form-item>
+        </el-col>
+      </el-row>
 
-        <div class="bj-divider"><div class="left"><span class="default-text">买家以及收货信息</span><div class="button"></div><span class="tips"></span></div></div>
-        <el-row>
-          <el-col :span="9">
-              <el-form-item  label-width="100px" label="买家昵称" prop="buyerNick" style="margin-right:20px">
-                 <el-input v-model="form.outerOrderNo" placeholder="请输入买家昵称" />
-              </el-form-item>
-          </el-col>
-         </el-row>
-        <el-row>
-          <el-col :span="9">
-              <el-form-item  label-width="100px" label="收货人" prop="logisticsUserName"  style="margin-right:20px">
-                 <el-input v-model="form.logisticsUserName" placeholder="请输入" />
-              </el-form-item>
-          </el-col>
+      <div class="bj-divider"><div class="left"><span class="default-text">买家以及收货信息</span><div class="button"></div><span class="tips"></span></div></div>
+      <el-row>
+        <el-col :span="9">
+          <el-form-item  label-width="100px" label="买家昵称" prop="buyerNick" style="margin-right:20px">
+            <el-input v-model="form.outerOrderNo" placeholder="请输入买家昵称" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="9">
+          <el-form-item  label-width="100px" label="收货人" prop="logisticsUserName"  style="margin-right:20px">
+            <el-input v-model="form.logisticsUserName" placeholder="请输入" />
+          </el-form-item>
+        </el-col>
 
-          <el-col :span="9">
-              <el-form-item  label-width="100px" label="收货人手机" prop="logisticsUserPhone">
-                 <el-input v-model="form.logisticsUserPhone" placeholder="请输入" />
-              </el-form-item>
-          </el-col>
+        <el-col :span="9">
+          <el-form-item  label-width="100px" label="收货人手机" prop="logisticsUserPhone">
+            <el-input v-model="form.logisticsUserPhone" placeholder="请输入" />
+          </el-form-item>
+        </el-col>
 
-          <el-col :span="9">
-              <el-form-item  label-width="95px" label="所属省市区" prop="cityIds">
-                  <el-cascader style="width:100%"
-                  ref="citySelect"
-                  v-model="cityIds"
-                  :options="citys"
-                  @change="handleCityChange"></el-cascader>
-               </el-form-item>
-          </el-col>
+        <el-col :span="9">
+          <el-form-item  label-width="95px" label="所属省市区" prop="cityIds">
+            <el-cascader style="width:100%"
+                         ref="citySelect"
+                         v-model="cityIds"
+                         :options="citys"
+                         @change="handleCityChange"></el-cascader>
+          </el-form-item>
+        </el-col>
 
-           <el-col :span="9">
-              <el-form-item  label-width="10px"  prop="cityIds">
-                  <el-input v-model="form.userAddress" placeholder="详情地址" /> 
-              </el-form-item>
-          </el-col>
+        <el-col :span="9">
+          <el-form-item  label-width="10px"  prop="cityIds">
+            <el-input v-model="form.userAddress" placeholder="详情地址" />
+          </el-form-item>
+        </el-col>
 
 
-           <el-col :span="18">
-                <el-form-item label-width="100px" label="买家备注" prop="buyerRemark" >
-                    <el-input type="textarea" v-model="form.buyerRemark" show-word-limit maxlength="200"></el-input>
-               </el-form-item>   
-           </el-col>
-        </el-row>
-  
-         <div class="bj-divider"><div class="left"><span class="default-text">支付信息</span><div class="button"></div><span class="tips"></span></div></div>
-         <el-row>
-          <el-col :span="8">
-               <el-form-item label="支付方式" prop="payType">
-                    <el-select  style="width:100%"  v-model="form.payType" placeholder="请选择支付方式" clearable size="small">
-                      <el-option
-                            v-for="item in payTypeOptions"
-                            :key="'payType'+item.dictValue"  
-                            :label="item.dictLabel"
-                            :value="item.dictValue"/>
-                    </el-select>
-                </el-form-item>
-          </el-col>
-          <el-col :span="8">
-              <el-form-item  label-width="100px" label="支付金额" prop="buyerPayAmount"  style="margin-right:20px">
-                 <el-input v-model="form.buyerPayAmount" type="number"  placeholder="请输入" /> 
-              </el-form-item>
-          </el-col>
-          <el-col :span="8">
-              <el-form-item  label-width="140px" label="支付金额是否含运费" prop="shippingType">
-                    <el-radio-group v-model="form.shippingType">
-                       <el-radio :key="'shippingType'+item.dictValue"    :label="item.dictValue" v-for="item in shippingTypeOptions" >{{item.dictLabel}}</el-radio>
-                    </el-radio-group>
-              </el-form-item>
-          </el-col>
-         </el-row>
-         <el-row>
-           <el-col :span="8">
-                <el-form-item  label="支付状态" prop="payStatus">
-                    <el-select  style="width:100%"   v-model="form.payStatus" placeholder="请选择" clearable size="small">
-                      <el-option
-                            v-for="item in payStatusOptions"
-                            :key="'payStatus'+item.dictValue"  
-                            :label="item.dictLabel"
-                            :value="item.dictValue"/>
-                    </el-select>
-                </el-form-item>
+        <el-col :span="18">
+          <el-form-item label-width="100px" label="买家备注" prop="buyerRemark" >
+            <el-input type="textarea" v-model="form.buyerRemark" show-word-limit maxlength="200"></el-input>
+          </el-form-item>
         </el-col>
+      </el-row>
 
-         <el-col :span="8">
-              <el-form-item label-width="100px" label="支付时间" prop="payTime" style="margin-right:20px">
-                  <el-date-picker clearable size="small" style="width:100%"
-                    v-model="form.payTime"
-                    type="date"
-                    value-format="yyyy-MM-dd"
-                    placeholder="请选择">
-                  </el-date-picker>
-              </el-form-item>
-          </el-col>
-        </el-row>
+      <div class="bj-divider"><div class="left"><span class="default-text">支付信息</span><div class="button"></div><span class="tips"></span></div></div>
+      <el-row>
+        <el-col :span="8">
+          <el-form-item label="支付方式" prop="payType">
+            <el-select  style="width:100%"  v-model="form.payType" placeholder="请选择支付方式" clearable size="small">
+              <el-option
+                v-for="item in payTypeOptions"
+                :key="'payType'+item.dictValue"
+                :label="item.dictLabel"
+                :value="item.dictValue"/>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item  label-width="100px" label="支付金额" prop="buyerPayAmount"  style="margin-right:20px">
+            <el-input v-model="form.buyerPayAmount" type="number"  placeholder="请输入" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item  label-width="140px" label="支付金额是否含运费" prop="shippingType">
+            <el-radio-group v-model="form.shippingType">
+              <el-radio :key="'shippingType'+item.dictValue"    :label="item.dictValue" v-for="item in shippingTypeOptions" >{{item.dictLabel}}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="8">
+          <el-form-item  label="支付状态" prop="payStatus">
+            <el-select  style="width:100%"   v-model="form.payStatus" placeholder="请选择" clearable size="small">
+              <el-option
+                v-for="item in payStatusOptions"
+                :key="'payStatus'+item.dictValue"
+                :label="item.dictLabel"
+                :value="item.dictValue"/>
+            </el-select>
+          </el-form-item>
+        </el-col>
 
-         <div class="bj-divider"><div class="left"><span class="default-text">优惠信息</span><div class="button"></div><span class="tips"></span></div></div>
-         <el-row>
-              <el-col :span="9">
-                  <el-form-item  label-width="100px" label="三方平台优惠" prop="isThirdDiscount" style="margin-right:10px">
-                        <el-radio-group v-model="form.isThirdDiscount">
-                          <el-radio :key="'isThirdDiscount'+item.dictValue"   :label="item.dictValue" v-for="item in isThirdDiscountOptions" >{{item.dictLabel}}</el-radio>
-                        </el-radio-group>
-                  </el-form-item>
-              </el-col>  
-              <el-col :span="9" v-if="form.isThirdDiscount!='0'">
-                    <el-form-item label-width="10" label="" prop="thirdDiscount">
-                      <el-input v-model="form.thirdDiscount" placeholder="三方平台优惠金额" />
-                    </el-form-item>
-              </el-col>
-               <el-col :span="9" v-if="form.isThirdDiscount!='0'">
-                  <el-form-item label-width="100px" label="优惠承担" prop="thirdDiscountAmount" style="margin-right:10px">
-                      <el-input v-model="form.thirdDiscountAmount" placeholder="三方平台承担优惠金额" />
-                    </el-form-item>
-              </el-col>  
-              <el-col :span="9" v-if="form.isThirdDiscount!='0'">
-                    <el-form-item label-width="10" label="" prop="businessDiscountAmount">
-                      <el-input v-model="form.businessDiscountAmount" placeholder="商家承担优惠金额" />
-                    </el-form-item>
-              </el-col>
-         </el-row>
+        <el-col :span="8">
+          <el-form-item label-width="100px" label="支付时间" prop="payTime" style="margin-right:20px">
+            <el-date-picker clearable size="small" style="width:100%"
+                            v-model="form.payTime"
+                            type="date"
+                            value-format="yyyy-MM-dd"
+                            placeholder="请选择">
+            </el-date-picker>
+          </el-form-item>
+        </el-col>
+      </el-row>
 
-         <div class="bj-divider" style="margin-bottom:10px">
-               <div class="left"><span class="default-text">商品信息</span>
-                   <el-button plain  type="primary"  size="mini"  @click="handleAddProduct">添加商品</el-button>
-                   <span class="tips"></span>
-              </div>
-          </div>
-          <el-row> 
-               <el-col :span="24">
-                  <el-form-item label="" label-width="0" prop="productList" style="margin-left:0" >
-                        <el-table border width="100%" style="margin-top:5px;"  :data="products">
-                          <el-table-column label="商品编号" align="center" prop="barCode" />
-                          <el-table-column label="商品图片" align="center" width="100">
-                            <template slot-scope="scope">
-                              <el-popover
-                                placement="right"
-                                title=""
-                                trigger="hover">
-                                <img slot="reference" :src="scope.row.image" width="50">
-                                <img :src="scope.row.image" style="max-width: 50px;">
-                              </el-popover>
-                            </template>
-                          </el-table-column>
-                          <el-table-column label="商品名称" show-overflow-tooltip align="center" prop="productName" />
-                          <el-table-column label="商品规格" align="center" prop="sku" />
-                          <!-- <el-table-column label="库存" align="center" prop="stock" /> -->
-                          <el-table-column label="售价" align="center"  prop="price"  >
-                            <template slot-scope="scope">
-                              <div v-if="scope.row.price!=null">
-                                  {{scope.row.price.toFixed(2)}}
-                              </div>
-                            </template>
-                          </el-table-column>
-                          <!-- <el-table-column label="代理价" align="center" prop="agentPrice" /> -->
-                          <el-table-column label="数量" align="center"  prop="count" width="200px" >
-                            <template slot-scope="scope">
-                              <div>
-                                  <el-input-number v-model="scope.row.count"  @change="handleProductCountChange(scope.row)"  size="mini" :min="1" :max="scope.row.stock"  ></el-input-number>
-                              </div>
-                            </template>
-                          </el-table-column>
-                          <el-table-column label="小计" align="center"  prop="money"  >
-                            <template slot-scope="scope">
-                              <div v-if="scope.row.money!=null">
-                                  {{scope.row.money.toFixed(2)}}
-                              </div>
-                            </template>
-                          </el-table-column>
-                          <el-table-column label="操作" align="center" width="100px" >
-                            <template slot-scope="scope">
-                              <el-button
-                                size="mini"
-                                type="text"
-                                icon="el-icon-delete"
-                                @click="handleProductDelete(scope.row)"
-                              >删除</el-button>
-                            </template>
-                          </el-table-column>
-                        </el-table>
-                        <!-- <el-row  >
-                          <el-col  >
-                                <span style="margin-left:10px;" v-if="totalMoney!=null">商品总价:{{totalMoney}}</span>
-                          </el-col>
-                        </el-row> -->
-                  </el-form-item>
-               </el-col> 
-          </el-row>
-      </el-form>
+      <div class="bj-divider"><div class="left"><span class="default-text">优惠信息</span><div class="button"></div><span class="tips"></span></div></div>
+      <el-row>
+        <el-col :span="9">
+          <el-form-item  label-width="100px" label="三方平台优惠" prop="isThirdDiscount" style="margin-right:10px">
+            <el-radio-group v-model="form.isThirdDiscount">
+              <el-radio :key="'isThirdDiscount'+item.dictValue"   :label="item.dictValue" v-for="item in isThirdDiscountOptions" >{{item.dictLabel}}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+        <el-col :span="9" v-if="form.isThirdDiscount!='0'">
+          <el-form-item label-width="10" label="" prop="thirdDiscount">
+            <el-input v-model="form.thirdDiscount" placeholder="三方平台优惠金额" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="9" v-if="form.isThirdDiscount!='0'">
+          <el-form-item label-width="100px" label="优惠承担" prop="thirdDiscountAmount" style="margin-right:10px">
+            <el-input v-model="form.thirdDiscountAmount" placeholder="三方平台承担优惠金额" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="9" v-if="form.isThirdDiscount!='0'">
+          <el-form-item label-width="10" label="" prop="businessDiscountAmount">
+            <el-input v-model="form.businessDiscountAmount" placeholder="商家承担优惠金额" />
+          </el-form-item>
+        </el-col>
+      </el-row>
 
-      <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="submitForm">确 定</el-button>
-        <el-button @click="cancel">取 消</el-button>
+      <div class="bj-divider" style="margin-bottom:10px">
+        <div class="left"><span class="default-text">商品信息</span>
+          <el-button plain  type="primary"  size="mini"  @click="handleAddProduct">添加商品</el-button>
+          <span class="tips"></span>
+        </div>
       </div>
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="" label-width="0" prop="productList" style="margin-left:0" >
+            <el-table border width="100%" style="margin-top:5px;"  :data="products">
+              <el-table-column label="商品编号" align="center" prop="barCode" />
+              <el-table-column label="商品图片" align="center" width="100">
+                <template slot-scope="scope">
+                  <el-popover
+                    placement="right"
+                    title=""
+                    trigger="hover">
+                    <img slot="reference" :src="scope.row.image" width="50">
+                    <img :src="scope.row.image" style="max-width: 50px;">
+                  </el-popover>
+                </template>
+              </el-table-column>
+              <el-table-column label="商品名称" show-overflow-tooltip align="center" prop="productName" />
+              <el-table-column label="商品规格" align="center" prop="sku" />
+              <!-- <el-table-column label="库存" align="center" prop="stock" /> -->
+              <el-table-column label="售价" align="center"  prop="price"  >
+                <template slot-scope="scope">
+                  <div v-if="scope.row.price!=null">
+                    {{scope.row.price.toFixed(2)}}
+                  </div>
+                </template>
+              </el-table-column>
+              <!-- <el-table-column label="代理价" align="center" prop="agentPrice" /> -->
+              <el-table-column label="数量" align="center"  prop="count" width="200px" >
+                <template slot-scope="scope">
+                  <div>
+                    <el-input-number v-model="scope.row.count"  @change="handleProductCountChange(scope.row)"  size="mini" :min="1" :max="scope.row.stock"  ></el-input-number>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="小计" align="center"  prop="money"  >
+                <template slot-scope="scope">
+                  <div v-if="scope.row.money!=null">
+                    {{scope.row.money.toFixed(2)}}
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" align="center" width="100px" >
+                <template slot-scope="scope">
+                  <el-button
+                    size="mini"
+                    type="text"
+                    icon="el-icon-delete"
+                    @click="handleProductDelete(scope.row)"
+                  >删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+            <!-- <el-row  >
+              <el-col  >
+                    <span style="margin-left:10px;" v-if="totalMoney!=null">商品总价:{{totalMoney}}</span>
+              </el-col>
+            </el-row> -->
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <div slot="footer" class="dialog-footer">
+      <el-button type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="cancel">取 消</el-button>
+    </div>
 
   </div>
 </template>
@@ -269,42 +269,42 @@ export default {
       form: {  },
       // 表单校验
       rules: {
-            outerOrderNo: [
-              { required: true, message: "请输入三方订单号", trigger: "blur" }
-            ],
-             orderTime: [
-              { required: true, message: "请选择三方下单时间", trigger: "blur" }
-            ],
-             channelType: [
-              { required: true, message: "请选择销售渠道", trigger: "blur" }
-            ],
-             storeId: [
-              { required: true, message: "请选择销售店铺", trigger: "blur" }
-            ],
-             buyerNick: [
-              { required: true, message: "请输入买家昵称", trigger: "blur" }
-            ],
-            logisticsUserName: [
-              { required: true, message: "请输入收货人", trigger: "blur" }
-            ],
-             logisticsUserPhone: [
-              { required: true, message: "请输入收货人手机号", trigger: "blur" }
-            ],
-            cityIds: [
-              { required: true, message: "请选择收货地址", trigger: "blur" }
-            ],
-            payType: [
-              { required: true, message: "请选择支付方式", trigger: "blur" }
-            ],
-            payStatus: [
-              { required: true, message: "请选择支付状态", trigger: "blur" }
-            ],
-            buyerPayAmount: [
-              { required: true, message: "请输入支付金额", trigger: "blur" }
-            ],
-            payTime: [
-              { required: true, message: "请选择支付时间", trigger: "blur" }
-            ],
+        outerOrderNo: [
+          { required: true, message: "请输入三方订单号", trigger: "blur" }
+        ],
+        orderTime: [
+          { required: true, message: "请选择三方下单时间", trigger: "blur" }
+        ],
+        channelType: [
+          { required: true, message: "请选择销售渠道", trigger: "blur" }
+        ],
+        storeId: [
+          { required: true, message: "请选择销售店铺", trigger: "blur" }
+        ],
+        buyerNick: [
+          { required: true, message: "请输入买家昵称", trigger: "blur" }
+        ],
+        logisticsUserName: [
+          { required: true, message: "请输入收货人", trigger: "blur" }
+        ],
+        logisticsUserPhone: [
+          { required: true, message: "请输入收货人手机号", trigger: "blur" }
+        ],
+        cityIds: [
+          { required: true, message: "请选择收货地址", trigger: "blur" }
+        ],
+        payType: [
+          { required: true, message: "请选择支付方式", trigger: "blur" }
+        ],
+        payStatus: [
+          { required: true, message: "请选择支付状态", trigger: "blur" }
+        ],
+        buyerPayAmount: [
+          { required: true, message: "请输入支付金额", trigger: "blur" }
+        ],
+        payTime: [
+          { required: true, message: "请选择支付时间", trigger: "blur" }
+        ],
       },
       channelTypeOptions:[],
       stores:[],
@@ -319,29 +319,29 @@ export default {
       isFreeShipping:false,
       products:[],
       product:{
-         open:false,
-         title:"商品选择"
+        open:false,
+        title:"商品选择"
       },
     };
   },
   created() {
-        this.getDicts("oms_channel_type").then((response) => {
-             this.channelTypeOptions = response.data;
-        });
-        this.getDicts("oms_pay_type").then((response) => {
-             this.payTypeOptions = response.data;
-        });
-        this.getDicts("oms_shipping_type").then((response) => {
-             this.shippingTypeOptions = response.data;
-        });
-        this.getDicts("oms_pay_status").then((response) => {
-             this.payStatusOptions = response.data;
-        });
-        this.getDicts("oms_is_third_discount").then((response) => {
-             this.isThirdDiscountOptions = response.data;
-        });
-        this.getCitys();
-        this.reset();
+    this.getDicts("oms_channel_type").then((response) => {
+      this.channelTypeOptions = response.data;
+    });
+    this.getDicts("oms_pay_type").then((response) => {
+      this.payTypeOptions = response.data;
+    });
+    this.getDicts("oms_shipping_type").then((response) => {
+      this.shippingTypeOptions = response.data;
+    });
+    this.getDicts("oms_pay_status").then((response) => {
+      this.payStatusOptions = response.data;
+    });
+    this.getDicts("oms_is_third_discount").then((response) => {
+      this.isThirdDiscountOptions = response.data;
+    });
+    this.getCitys();
+    this.reset();
   },
   methods: {
     // 取消按钮
@@ -405,13 +405,13 @@ export default {
         this.form = response.data;
         this.form.channelWarehouseName=row.channelWarehouseName;
         if(this.form.channelType!=null){
-            this.form.channelType = this.form.channelType.toString();
+          this.form.channelType = this.form.channelType.toString();
         }
         if(this.form.storeType!=null){
-            this.form.storeType = this.form.storeType.toString();
+          this.form.storeType = this.form.storeType.toString();
         }
         if(this.form.status!=null){
-            this.form.status = this.form.status.toString();
+          this.form.status = this.form.status.toString();
         }
         this.open = true;
         this.title = "修改网店";
@@ -432,9 +432,9 @@ export default {
           } else {
             addStore(this.form).then(response => {
               if (response.code === 200) {
-                  this.msgSuccess("新增成功");
-                  this.open = false;
-                  this.getList();
+                this.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
               }
             });
           }
@@ -445,61 +445,61 @@ export default {
     handleDelete(row) {
       const storeIds = row.storeId || this.ids;
       this.$confirm('是否确认删除网店编号为"' + storeIds + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return delStore(storeIds);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("删除成功");
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delStore(storeIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
     },
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有网店数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return exportStore(queryParams);
-        }).then(response => {
-          this.download(response.msg);
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return exportStore(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+      }).catch(function() {});
     },
     getChanelType(type){
       var chanelTypeName="";
       const chanelType = this.channelTypeOptions.filter((obj) => {
-            return obj[dictValue] === type
+        return obj[dictValue] === type
       });
       if(chanelType!=null){
-          chanelTypeName=chanelType.dictLable;
+        chanelTypeName=chanelType.dictLable;
       }
       return chanelTypeName;
     },
     channelChange(e){
-        var item=this.channelTypeOptions.find(val => val.dictValue === e);
-        var channelType=item.dictValue;
-        this.form.channelType=channelType;
-        listStore({"channelType":channelType}).then(response => {
-            this.stores = response.rows;
-        });
-        this.storeAvailable=false;
-        this.storePlaceholder="请选择";
+      var item=this.channelTypeOptions.find(val => val.dictValue === e);
+      var channelType=item.dictValue;
+      this.form.channelType=channelType;
+      listStore({"channelType":channelType}).then(response => {
+        this.stores = response.rows;
+      });
+      this.storeAvailable=false;
+      this.storePlaceholder="请选择";
     },
     handleCityChange(value) {
-          console.log(value);
-          var nodes=this.$refs.citySelect.getCheckedNodes();
-          this.form.cityIds=nodes[0].pathLabels[0]+"-"+nodes[0].pathLabels[1]+"-"+nodes[0].pathLabels[2];
-          this.form.cityIdsStr=value.toString();
-          console.log(this.form.cityIdsStr);
+      console.log(value);
+      var nodes=this.$refs.citySelect.getCheckedNodes();
+      this.form.cityIds=nodes[0].pathLabels[0]+"-"+nodes[0].pathLabels[1]+"-"+nodes[0].pathLabels[2];
+      this.form.cityIdsStr=value.toString();
+      console.log(this.form.cityIdsStr);
     },
     getCitys(){
-          getCitys().then(res => {
-             this.loading = false;
-             this.citys=res.data;
-          });
+      getCitys().then(res => {
+        this.loading = false;
+        this.citys=res.data;
+      });
     },
     handleProductCountChange(row){
       row.money=row.count*row.price;
@@ -507,14 +507,14 @@ export default {
       this.compute();
     },
     handleProductDelete(row){
-       this.products.splice(this.products.findIndex(item => item.id === row.id), 1)
-       this.compute();
+      this.products.splice(this.products.findIndex(item => item.id === row.id), 1)
+      this.compute();
     },
     compute(){
       this.totalMoney=0;
       var that=this;
       this.products.forEach (function (value) {
-          that.totalMoney += value.money;
+        that.totalMoney += value.money;
       });
       that.totalMoney=that.totalMoney.toFixed(2);
     },
@@ -540,7 +540,7 @@ export default {
 };
 </script>
 
-<style lang="scss" scoped> 
+<style lang="scss" scoped>
 
 .app-main{
   background: #ffffff;
@@ -549,89 +549,89 @@ export default {
   margin: 0;
 }
 .bj-divider {
-    padding: 10px 0;
-    border-bottom: 1px solid #eee;
-    margin-bottom: 16px;
-    display: -webkit-box;
-    display: -ms-flexbox;
-    display: flex;
-    -webkit-box-align: center;
-    -ms-flex-align: center;
-    align-items: center;
-    -webkit-box-pack: justify;
-    -ms-flex-pack: justify;
-    justify-content: space-between;
-    width: 100%
+  padding: 10px 0;
+  border-bottom: 1px solid #eee;
+  margin-bottom: 16px;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-align: center;
+  -ms-flex-align: center;
+  align-items: center;
+  -webkit-box-pack: justify;
+  -ms-flex-pack: justify;
+  justify-content: space-between;
+  width: 100%
 }
 .bj-divider.small {
-    font-size: 14px;
-    padding: 8px 0;
-    margin-bottom: 8px
+  font-size: 14px;
+  padding: 8px 0;
+  margin-bottom: 8px
 }
 .bj-divider.noPadding {
-    padding: 0
+  padding: 0
 }
 .bj-divider .left {
-    display: -webkit-box;
-    display: -ms-flexbox;
-    display: flex;
-    -webkit-box-align: center;
-    -ms-flex-align: center;
-    align-items: center
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-align: center;
+  -ms-flex-align: center;
+  align-items: center
 }
 .bj-divider .button {
-    display: -webkit-box;
-    display: -ms-flexbox;
-    display: flex
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex
 }
 .bj-divider .default-text {
-    border-left: 4px #13c2c2 solid;
-    padding-left: 16px;
-    font-size: 18px
+  border-left: 4px #409eff solid;
+  padding-left: 16px;
+  font-size: 18px
 }
 .bj-divider.small .default-text {
-    font-size: 14px
+  font-size: 14px
 }
 .bj-divider .xs {
-    font-size: 12px;
-    padding: 0 8px
+  font-size: 12px;
+  padding: 0 8px
 }
 .bj-divider .tips {
-    line-height: 20px;
-    margin-left: 16px
+  line-height: 20px;
+  margin-left: 16px
 }
 .bj-divider .right {
-    display: -webkit-box;
-    display: -ms-flexbox;
-    display: flex;
-    -webkit-box-align: center;
-    -ms-flex-align: center;
-    align-items: center
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-align: center;
+  -ms-flex-align: center;
+  align-items: center
 }
 .bj-divider .el-button {
-    margin-left: 16px
+  margin-left: 16px
 }
 .bj-divider .fixed {
-    position: fixed;
-    top: 55px;
-    z-index: 9;
-    right: 20px
+  position: fixed;
+  top: 55px;
+  z-index: 9;
+  right: 20px
 }
 
 .bj-divider .right .el-form-item--small.el-form-item {
-    margin-bottom: 0
+  margin-bottom: 0
 }
 
 .dialog-footer{
-    text-align: right;
-    position: fixed;
-    right: 0px;
-    bottom: 0px;
-    width: 90%;
-    margin-right: 0px;
-    padding:10px 20px;
-    z-index: 9;
-    background: #ffffff;
-    height: 65px;
+  text-align: right;
+  position: fixed;
+  right: 0px;
+  bottom: 0px;
+  width: 90%;
+  margin-right: 0px;
+  padding:10px 20px;
+  z-index: 9;
+  background: #ffffff;
+  height: 65px;
 }
-</style>
+</style>

+ 59 - 59
src/views/qw/forbiddenMessage/index.vue

@@ -2,12 +2,12 @@
   <div class="app-container">
     <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
       <el-form-item label="公司名" prop="companyId">
-          <el-select filterable  v-model="queryParams.companyId" placeholder="请选择公司名" clearable size="small">
-              <el-option
-                  v-for="item in companys"
-                  :key="item.companyId"
-                  :label="item.companyName"
-                  :value="item.companyId" />
+        <el-select filterable  v-model="queryParams.companyId" placeholder="请选择公司名" clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId" />
         </el-select>
       </el-form-item>
       <el-form-item label="员工" prop="companyUserName">
@@ -28,7 +28,7 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      
+
       <el-form-item label="消息编号" prop="id">
         <el-input
           v-model="queryParams.id"
@@ -75,10 +75,10 @@
           v-hasPermi="['qw:forbiddenMessage:export']"
         >导出</el-button>
       </el-col>
-	  <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table v-loading="loading" border :data="forbiddenMessageList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" border :data="forbiddenMessageList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="40" align="center" />
       <el-table-column label="公司" width="200" align="center" prop="companyName" />
       <el-table-column label="员工" width="100" align="center" prop="companyUserName" />
@@ -140,8 +140,8 @@
     </el-dialog>
 
     <el-dialog :visible.sync="dialogContextVisible" width="70%">
-        <SegRecords :qwUser="qwUser" :conversationId="conversationId"  :isContext=true :msgType=0  ref="sIMUI4"></SegRecords>
-  </el-dialog>
+      <SegRecords :qwUser="qwUser" :conversationId="conversationId"  :isContext=true :msgType=0  ref="sIMUI4"></SegRecords>
+    </el-dialog>
 
 
   </div>
@@ -158,9 +158,9 @@ import SegRecords from '@/components/LemonUI/components/chatrecords/segrecords.v
 export default {
   name: "ForbiddenMessage",
   components: {
-       Editor,
-       SegRecords
-   },
+    Editor,
+    SegRecords
+  },
   data() {
     return {
       // 遮罩层
@@ -211,19 +211,19 @@ export default {
   },
   methods: {
     convertTimestampToTime(timestamp) {
-    const milliseconds = timestamp * 1000; // 将时间戳乘以1000以转换为毫秒
-    // 创建一个新的Date对象,并将时间戳作为参数传入构造函数
-    const date = new Date(milliseconds);
-    // 使用Date对象的方法获取日期和时间
-    const year = date.getFullYear();
-    const month = date.getMonth() + 1;
-    const day = date.getDate();
-    const hours = date.getHours();
-    const minutes = date.getMinutes();
-    const seconds = date.getSeconds();
-    // 返回转换后的日期和时间字符串
-    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
-  },
+      const milliseconds = timestamp * 1000; // 将时间戳乘以1000以转换为毫秒
+      // 创建一个新的Date对象,并将时间戳作为参数传入构造函数
+      const date = new Date(milliseconds);
+      // 使用Date对象的方法获取日期和时间
+      const year = date.getFullYear();
+      const month = date.getMonth() + 1;
+      const day = date.getDate();
+      const hours = date.getHours();
+      const minutes = date.getMinutes();
+      const seconds = date.getSeconds();
+      // 返回转换后的日期和时间字符串
+      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+    },
     /** 查询企微违禁消息列表 */
     getList() {
       this.loading = true;
@@ -290,18 +290,18 @@ export default {
       });
     },
     handleLookMsg(row){
-          console.log("qxj row:"+JSON.stringify(row));
-          this.qwUser.userId=row.qwUserId;
-          this.qwUser.nickName=row.nickName;
-          this.qwUser.avatar=row.avatar;
-          this.conversationId=row.conversationId;
-          this.dialogContextVisible=true;
-          setTimeout(() => {
-                const sIMUI = this.$refs["sIMUI4"];
-                console.log("点击了上下文", sIMUI);
-                let message={id:row.messageId};
-                sIMUI.openContextBox(message);
-          }, 50);
+      console.log("qxj row:"+JSON.stringify(row));
+      this.qwUser.userId=row.qwUserId;
+      this.qwUser.nickName=row.nickName;
+      this.qwUser.avatar=row.avatar;
+      this.conversationId=row.conversationId;
+      this.dialogContextVisible=true;
+      setTimeout(() => {
+        const sIMUI = this.$refs["sIMUI4"];
+        console.log("点击了上下文", sIMUI);
+        let message={id:row.messageId};
+        sIMUI.openContextBox(message);
+      }, 50);
     },
     /** 提交按钮 */
     submitForm() {
@@ -331,28 +331,28 @@ export default {
     handleDelete(row) {
       const ids = row.id || this.ids;
       this.$confirm('是否确认删除企微违禁消息编号为"' + ids + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return delForbiddenMessage(ids);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("删除成功");
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delForbiddenMessage(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
     },
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有企微违禁消息数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return exportForbiddenMessage(queryParams);
-        }).then(response => {
-          this.download(response.msg);
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return exportForbiddenMessage(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+      }).catch(function() {});
     }
   }
 };
@@ -360,8 +360,8 @@ export default {
 
 
 <style lang="scss" scoped>
-    /deep/.el-dialog__headerbtn{
-         top:10px;
-    }
+/deep/.el-dialog__headerbtn{
+  top:10px;
+}
 
 </style>

+ 427 - 0
src/views/qw/friendMaterial/index.vue

@@ -0,0 +1,427 @@
+<template>
+  <div v-if="type == 'image'">
+    <ul v-for="(item,index) in value" :key="index" class="el-upload-list el-upload-list--picture-card">
+      <li tabindex="0" class="el-upload-list__item is-ready" :style="'width: '+width+'px;height: '+height+'px'">
+        <div>
+          <img :src="item" alt="" class="el-upload-list__item-thumbnail">
+          <span class="el-upload-list__item-actions">
+            <span v-if="index != 0" class="el-upload-list__item-preview" @click="moveMaterial(index,'up')">
+              <i class="el-icon-back" />
+            </span>
+            <span class="el-upload-list__item-preview" @click="zoomMaterial(index)">
+              <i class="el-icon-view" />
+            </span>
+            <span class="el-upload-list__item-delete" @click="deleteMaterial(index)">
+              <i class="el-icon-delete" />
+            </span>
+            <span v-if="index != value.length-1" class="el-upload-list__item-preview" @click="moveMaterial(index,'down')">
+              <i class="el-icon-right" />
+            </span>
+          </span>
+        </div>
+      </li>
+    </ul>
+    <div v-if="num > value.length" tabindex="0" class="el-upload el-upload--picture-card" :style="'width: '+width+'px;height: '+height+'px;'+'line-height:'+height+'px;'" @click="toSeleteMaterial">
+      <i class="el-icon-plus" />
+    </div>
+    <!-- 查看 -->
+    <el-dialog
+      append-to-body
+      :visible.sync="dialogVisible"
+      width="35%"
+    >
+      <img :src="materialUrl" alt="" style="width: 100%">
+    </el-dialog>
+    <!-- 素材列表 -->
+    <el-dialog
+      title="朋友圈图片素材库"
+      append-to-body
+      :visible.sync="listDialogVisible"
+      width="70%"
+    >
+      <el-container>
+        <el-aside width="unset">
+          <div style="margin-bottom: 10px">
+            <el-button
+              class="el-icon-plus"
+              size="small"
+              @click="materialgroupAdd()"
+            >
+              添加分组
+            </el-button>
+          </div>
+          <div class="group-list">
+            <div class="group-item" v-for="(group) in materialGroupList">
+                <el-button  @click="selectGroup(group)" type="primary" plain >{{group.materialGroupName}}</el-button>
+            </div>
+          </div>
+        </el-aside>
+        <el-main>
+          <el-card>
+            <div slot="header">
+              <el-row>
+                <el-col :span="12">
+                  <span>{{ materialGroup.materialGroupName }}</span>
+                  <span v-if="materialGroup.materialGroupId >0">
+                    <el-button size="small" type="text" class="el-icon-edit" style="margin-left: 10px;" @click="materialgroupEdit(materialGroup)">重命名</el-button>
+                    <el-button size="small" type="text" class="el-icon-delete" style="margin-left: 10px;color: red" @click="materialgroupDelete(materialGroup)">删除</el-button>
+                  </span>
+                </el-col>
+                <el-col :span="12" style="text-align: right;">
+                  <el-upload
+                    :action="uploadUrl"
+                    :file-list="[]"
+                    :on-progress="handleProgress"
+                    :before-upload="beforeUpload"
+                    :on-success="handleSuccess"
+                    :data="{type: 1}"
+                    multiple
+                  >
+                    <el-button size="small" type="primary">批量上传</el-button>
+                  </el-upload>
+                </el-col>
+              </el-row>
+            </div>
+            <div v-loading="tableLoading">
+              <el-alert
+                v-if="tableData.length <= 0"
+                title="暂无数据"
+                type="info"
+                :closable="false"
+                center
+                show-icon
+              />
+              <el-row :gutter="5">
+                <el-checkbox-group v-model="urls" :max="num - value.length">
+                  <el-col v-for="(item,index) in tableData" :key="index" :span="4">
+                    <el-card :body-style="{ padding: '5px' }">
+                      <el-image
+                        style="width: 100%;height: 100px"
+                        :src="item.materialUrl"
+                        fit="contain"
+                        :preview-src-list="[item.materialUrl]"
+                        :z-index="9999"
+                      />
+                      <div>
+                        <el-checkbox class="material-name" :label="item.materialUrl">
+                          选择
+                        </el-checkbox>
+                        <el-row>
+                          <el-col :span="24" class="col-do">
+                            <el-button type="text" size="medium" @click="materialDel(item)">删除</el-button>
+                          </el-col>
+                        </el-row>
+
+                      </div>
+                    </el-card>
+                  </el-col>
+                </el-checkbox-group>
+              </el-row>
+               <pagination
+                  v-show="total>0"
+                  :total="total"
+                  :page.sync="queryParams.pageNum"
+                  :limit.sync="queryParams.pageSize"
+                  @pagination="getMaterialList"
+                />
+
+            </div>
+          </el-card>
+        </el-main>
+      </el-container>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="listDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submit">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {addMaterial, delMaterial, listMaterial,} from "@/api/qw/material";
+import {addMaterialGroup, delMaterialGroup, listMaterialGroup, updateMaterialGroup} from "@/api/qw/materialGroup";
+
+export default {
+  name: 'ImageSelect',
+  props: {
+    // 素材数据
+    value: {
+      type: Array,
+      default() {
+        return []
+      }
+    },
+    // 素材类型
+    type: {
+      type: String
+    },
+    // 素材限制选择数量,最多9个
+    num: {
+      type: Number,
+      default() {
+        return 9
+      }
+    },
+    // 宽度
+    width: {
+      type: Number,
+      default() {
+        return 150
+      }
+    },
+    // 宽度
+    height: {
+      type: Number,
+      default() {
+        return 150
+      }
+    }
+  },
+  data() {
+    return {
+      uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS",
+      dialogVisible: false,
+      materialUrl: '',
+      listDialogVisible: false,
+      //素材组列表
+      materialGroupList: [],
+      materialGroupLoading: false,
+      //选择的某个素材组
+      materialGroup:{},
+      tableData: [],
+      resultNumber: 0,
+      total: 0,
+      //查询素材列表
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        groupType:2,
+        materialGroupName: null,
+        materialGroupId:null,
+        materialUrl: null,
+        createUserId: null,
+      },
+      tableLoading: false,
+      urls: []
+    }
+  },
+  mounted(){
+    this.getAllMaterialGroup();
+  },
+  methods: {
+    //查询素材组下的素材列表
+    selectGroup(item){
+      this.materialGroup=item;
+      this.queryParams.materialGroupId=item.materialGroupId;
+      this.getMaterialList();
+    },
+    //添加分组
+    materialgroupAdd() {
+      const that = this
+      this.$prompt('请输入分组名', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消'
+      }).then(({ value }) => {
+        addMaterialGroup({
+          materialGroupName: value,
+          groupType:2
+        }).then(function() {
+          that.materialGroup={};
+          that.getAllMaterialGroup();
+        })
+      }).catch(() => {
+
+      })
+    },
+    //删除素材组
+    materialgroupDelete(materialgroupObj) {
+      const that = this
+      this.$confirm('是否确认删除该分组?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(function() {
+        delMaterialGroup(materialgroupObj.materialGroupId)
+          .then(function() {
+            that.materialGroup={};
+            that.getAllMaterialGroup()
+          })
+      })
+      this.resetGroup();
+      this.getAllMaterialGroup();
+    },
+    //修改素材组名称
+    materialgroupEdit(materialgroupObj) {
+      const that = this
+      this.$prompt('请输入分组名', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        inputValue: materialgroupObj.materialGroupName
+      }).then(({ value }) => {
+        updateMaterialGroup({
+          materialGroupId: materialgroupObj.materialGroupId,
+          materialGroupName: value,
+          groupType:2,
+        }).then(function() {
+          that.materialGroup={};
+          that.getAllMaterialGroup()
+        })
+      }).catch(() => {
+
+      })
+    },
+    //获取所有素材分组
+    getAllMaterialGroup() {
+      this.materialGroupLoading = true;
+      listMaterialGroup({groupType:2}).then(response => {
+        this.materialGroupList = response.rows
+        this.materialGroupLoading = false;
+      });
+    },
+    resetGroup(){
+      this.queryParams= {
+        pageNum: 1,
+          pageSize: 10,
+          groupType:2,
+          materialGroupName: null,
+          materialGroupId:null,
+          materialUrl: null,
+          createUserId: null,
+      };
+    },
+    //查询分组 下的素材
+    getMaterialList() {
+      this.tableLoading = true;
+      listMaterial(this.queryParams).then(response => {
+        this.tableData = response.rows;
+        this.total = response.total;
+        this.tableLoading = false;
+      });
+    },
+    //移动素材
+    moveMaterial(index, type) {
+      if (type == 'up') {
+        const tempOption = this.value[index - 1]
+        this.$set(this.value, index - 1, this.value[index])
+        this.$set(this.value, index, tempOption)
+      }
+      if (type == 'down') {
+        const tempOption = this.value[index + 1]
+        this.$set(this.value, index + 1, this.value[index])
+        this.$set(this.value, index, tempOption)
+      }
+    },
+    //缩小图片
+    zoomMaterial(index) {
+      this.dialogVisible = true
+      this.materialUrl = this.value[index]
+    },
+    //删除素材
+    deleteMaterial(index) {
+      const that = this
+      this.$confirm('是否确认删除?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(function() {
+        that.value.splice(index, 1)
+        that.urls = []
+      })
+    },
+
+    toSeleteMaterial() {
+      this.listDialogVisible = true
+      this.getAllMaterialGroup()
+      this.getMaterialList();
+    },
+    //删除素材
+    materialDel(item) {
+      const that = this
+      this.$confirm('是否确认删除该素材?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(function() {
+        delMaterial(item.materialId)
+          .then(function() {
+            that.queryParams.pageNum=0;
+            that.getMaterialList();
+          })
+      })
+    },
+
+    //上传进度
+    handleProgress(event, file, fileList) {
+    },
+    //上传成功后
+    handleSuccess(response, file, fileList) {
+      const that = this
+      addMaterial({
+        materialType: 'image',
+        materialGroupId: this.queryParams.materialGroupId,
+        materialName: file.name,
+        materialUrl: response.url,
+        groupType:2,
+      }).then(() => {
+        this.resultNumber++
+        if (fileList.length === this.resultNumber) {
+          that.getMaterialList()
+          this.resultNumber = 0
+        }
+      })
+    },
+    //上传之前
+    beforeUpload(file) {
+      const isPic =
+        file.type === 'image/jpeg' ||
+        file.type === 'image/png' ||
+        file.type === 'image/gif' ||
+        file.type === 'image/jpg'
+      const isLt2M = file.size / 1024 / 1024 < 10
+      if (!isPic) {
+        this.$message.error('上传图片只能是 JPG、JPEG、PNG、GIF 格式!')
+        return false
+      }
+      if (!isLt2M) {
+        this.$message.error('上传图片大小不能超过 10MB!')
+      }
+      return isPic && isLt2M
+    },
+    //提交
+    submit() {
+      this.urls.forEach(item => {
+        console.log("item",item)
+        console.log("this.value",this.value)
+        console.log("this.value.length",this.value.length)
+
+        this.$set(this.value, this.value.length, item)
+      })
+      this.listDialogVisible = false
+    }
+  }
+}
+</script>
+
+<style rel="stylesheet/scss" lang="scss" scoped>
+  ::v-deep .el-icon-circle-close{
+    color: red;
+  }
+  .material-name{
+    padding: 8px 0px;
+  }
+  .col-do{
+    text-align: center;
+  }
+  .button-do{
+    padding: unset!important;
+    font-size: 12px;
+  }
+  .group-list{
+    display: flex;
+    flex-direction:column;
+    align-items: flex-start;
+  }
+  .group-item{
+    margin: 5px;
+  }
+</style>

+ 41 - 41
src/views/qw/qwCompany/index.vue

@@ -84,7 +84,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table v-loading="loading" :data="qwCompanyList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="qwCompanyList" @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="企业CorpID" align="center" prop="corpId" />
@@ -144,7 +144,7 @@
         <el-form-item label="服通讯录Secret" prop="serverBookSecret">
           <el-input v-model="form.serverBookSecret" placeholder="请输入服务商的通讯录Secret" />
         </el-form-item>
-       <el-form-item label="应用Secret" prop="openSecret">
+        <el-form-item label="应用Secret" prop="openSecret">
           <el-input v-model="form.openSecret" placeholder="请输入服务商应用的Secret" />
         </el-form-item>
         <!-- <el-form-item label="服务商应用的企业id" prop="openCorpId">
@@ -186,12 +186,12 @@
         <el-form-item label="关联公司" prop="companyIds">
 
           <el-select filterable  v-model="form.companyIds" remote multiple placeholder="请选择公司名"  clearable style="width: 50%;">
-                <el-option
-                  v-for="item in companys"
-                  :key="item.companyId"
-                  :label="item.companyName"
-                  :value="item.companyId"
-                />
+            <el-option
+              v-for="item in companys"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
           </el-select>
 
 
@@ -220,20 +220,20 @@ import { getCompanyList } from "@/api/company/company";
 export default {
   name: "QwCompany",
   watch: {
-      'form.realmNameUrl': function(newVal) {
-        if(newVal==null){
-          this.form.notifyUrl="输入域名地址生成";
-        }else{
-           this.form.notifyUrl = "http://"+newVal+"/qw/data/"+this.form.corpId;
-        }
+    'form.realmNameUrl': function(newVal) {
+      if(newVal==null){
+        this.form.notifyUrl="输入域名地址生成";
+      }else{
+        this.form.notifyUrl = "http://"+newVal+"/qw/data/"+this.form.corpId;
+      }
 
-      },
-      'form.corpId': function(newVal) {
-          if (this.form.realmNameUrl !== null) {
-            this.form.notifyUrl = this.form.realmNameUrl + "/qw/data/" + newVal;
-          }
-        }
     },
+    'form.corpId': function(newVal) {
+      if (this.form.realmNameUrl !== null) {
+        this.form.notifyUrl = this.form.realmNameUrl + "/qw/data/" + newVal;
+      }
+    }
+  },
   data() {
     return {
       // 遮罩层
@@ -297,7 +297,7 @@ export default {
       this.statusOptions = response.data;
     });
     getCompanyList().then(response => {
-        this.companys = response.data;
+      this.companys = response.data;
 
     });
   },
@@ -382,7 +382,7 @@ export default {
       this.$refs["form"].validate(valid => {
         if (valid) {
           console.log(this.form.companyIds)
-           this.form.companyIds = (this.form.companyIds).join(',');
+          this.form.companyIds = (this.form.companyIds).join(',');
           if (this.form.id != null) {
             updateQwCompany(this.form).then(response => {
               this.msgSuccess("修改成功");
@@ -403,30 +403,30 @@ export default {
     handleDelete(row) {
       const ids = row.id || this.ids;
       this.$confirm('是否确认删除企微主体编号为"' + ids + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return delQwCompany(ids);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("删除成功");
-        }).catch(() => {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delQwCompany(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {});
     },
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有企微主体数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(() => {
-          this.exportLoading = true;
-          return exportQwCompany(queryParams);
-        }).then(response => {
-          this.download(response.msg);
-          this.exportLoading = false;
-        }).catch(() => {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportQwCompany(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
     }
   }
 };

+ 213 - 0
src/views/qw/sop/ImageUpload.vue

@@ -0,0 +1,213 @@
+<template>
+  <div class="component-upload-image">
+    <el-upload
+      :action="uploadUrl"
+      list-type="picture-card"
+      :on-success="handleUploadSuccess"
+      :before-upload="handleBeforeUpload"
+      :limit="limit"
+      :on-error="handleUploadError"
+      :on-exceed="handleExceed"
+      name="file"
+      :disabled="disabled"
+      :on-remove="handleRemove"
+      :show-file-list="true"
+      :file-list="fileList"
+      :on-preview="handlePictureCardPreview"
+      :class="{hide: this.fileList.length >= this.limit}"
+    >
+      <i class="el-icon-plus"></i>
+    </el-upload>
+
+    <el-dialog
+      :visible.sync="dialogVisible"
+      title="预览"
+      width="800"
+      append-to-body
+    >
+      <img
+        :src="dialogImageUrl"
+        style="display: block; max-width: 100%; margin: 0 auto"
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getToken } from "@/utils/auth";
+
+export default {
+  name: "ImageUpload",
+  props: {
+    value: [String, Object, Array],
+    // 图片数量限制
+    limit: {
+      type: Number,
+      default: 1,
+    },
+    // 大小限制(MB)
+    fileSize: {
+       type: Number,
+       default: 1,
+    },
+    // 文件类型, 例如['png', 'jpg', 'jpeg']
+    fileType: {
+      type: Array,
+      default: () => ["png", "jpg", "jpeg"],
+    },
+    // 大小限制(MB)
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
+    // 是否显示提示
+    isShowTip: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {
+      dialogImageUrl: "",
+      dialogVisible: false,
+      hideUpload: false,
+      baseUrl:"",
+      uploadUrl: process.env.VUE_APP_BASE_API + "/common/uploadOSS",
+      headers: {
+        Authorization: "Bearer " + getToken(),
+      },
+      fileList: []
+    };
+  },
+  watch: {
+    value: {
+      handler(val) {
+        if (val) {
+          // 首先将值转为数组
+          const list = Array.isArray(val) ? val : this.value.split(',');
+          // 然后将数组转为对象数组
+          this.fileList = list.map(item => {
+            if (typeof item === "string") {
+              if (item.indexOf(this.baseUrl) === -1) {
+                  item = { name: item, url: item };
+              } else {
+                  item = { name: item, url: item };
+              }
+            }
+            return item;
+          });
+        } else {
+          this.fileList = [];
+          return [];
+        }
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  computed: {
+    // 是否显示提示
+    showTip() {
+      return this.isShowTip && (this.fileType || this.fileSize);
+    },
+  },
+  methods: {
+    // 删除图片
+    handleRemove(file, fileList) {
+      const findex = this.fileList.map(f => f.name).indexOf(file.name);
+      if(findex > -1) {
+        this.fileList.splice(findex, 1);
+        this.$emit("input", this.listToString(this.fileList));
+        this.$emit("change", this.listToString(this.fileList));    // 新增change事件触发
+      }
+    },
+    // 上传成功回调
+    handleUploadSuccess(res,file) {
+      this.fileList.push({ name: res.url, url: res.url });
+      this.$emit("input", this.listToString(this.fileList));
+      this.$emit("change", this.listToString(this.fileList));    // 新增change事件触发
+      this.loading.close();
+    },
+    // 上传前loading加载
+    handleBeforeUpload(file) {
+      let isImg = false;
+      if (this.fileType.length) {
+        let fileExtension = "";
+        if (file.name.lastIndexOf(".") > -1) {
+          fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
+        }
+        isImg = this.fileType.some(type => {
+          if (file.type.indexOf(type) > -1) return true;
+          if (fileExtension && fileExtension.indexOf(type) > -1) return true;
+          return false;
+        });
+      } else {
+        isImg = file.type.indexOf("image") > -1;
+      }
+
+      if (!isImg) {
+        this.$message.error(
+          `文件格式不正确, 请上传${this.fileType.join("/")}图片格式文件!`
+        );
+        return false;
+      }
+      if (this.fileSize) {
+        const isLt = file.size / 1024 / 1024 < this.fileSize;
+        if (!isLt) {
+          this.$message.error(`上传头像图片大小不能超过 ${this.fileSize} MB!`);
+          return false;
+        }
+      }
+      this.loading = this.$loading({
+        lock: true,
+        text: "上传中",
+        background: "rgba(0, 0, 0, 0.7)",
+      });
+    },
+    // 文件个数超出
+    handleExceed() {
+      this.$message.error(`上传文件数量不能超过 ${this.limit} 个!`);
+    },
+    // 上传失败
+    handleUploadError() {
+      this.$message({
+        type: "error",
+        message: "上传失败",
+      });
+      this.loading.close();
+    },
+    // 预览
+    handlePictureCardPreview(file) {
+      console.log(file)
+      this.dialogImageUrl = file.url;
+      this.dialogVisible = true;
+    },
+    // 对象转成指定字符串分隔
+    listToString(list, separator) {
+      let strs = "";
+      separator = separator || ",";
+      for (let i in list) {
+        strs += list[i].url.replace(this.baseUrl, "") + separator;
+      }
+      return strs != '' ? strs.substr(0, strs.length - 1) : '';
+    }
+  }
+};
+</script>
+<style scoped lang="scss">
+// .el-upload--picture-card 控制加号部分
+::v-deep.hide .el-upload--picture-card {
+    display: none;
+}
+// 去掉动画效果
+::v-deep .el-list-enter-active,
+::v-deep .el-list-leave-active {
+    transition: all 0s;
+}
+
+::v-deep .el-list-enter, .el-list-leave-active {
+    opacity: 0;
+    transform: translateY(0);
+}
+</style>
+

+ 331 - 0
src/views/qw/sopTemp/addAiChatTemp.vue

@@ -0,0 +1,331 @@
+<template>
+  <div class="app-container">
+    <div style="margin: 30px;">sop规则【新客对话】模板</div>
+    <div style="margin-top: 10px;margin-left: 50px;margin-right: 100px;margin-bottom: 60px;">
+      <el-form ref="formRef" :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="推送方式">
+          <el-radio-group v-model="form.sendType">
+            <el-radio
+              v-for="dict in sysQwSopType.filter(item => parseInt(item.dictValue) === 4)"
+              :key="dict.dictValue"
+              :label="parseInt(dict.dictValue)"
+            >{{ dict.dictLabel }}
+            </el-radio>
+          </el-radio-group>
+        </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="规则" prop="setting">
+          <el-timeline>
+            <el-timeline-item
+              :timestamp="'第'+(1+(form.gap*index))+'天'"
+              :color="'#0bbd87'"
+              placement="top"
+              v-for="(item, index) in setting"
+              :key="index"
+              style="margin-top: 10px;">
+              <el-row>
+                <el-col :span="22">
+                  <div style="background-color: #fbfbfb;padding: 15px; border: 1px solid #e6e6e6; margin-bottom: 20px;">
+                    <el-form :model="item" label-width="80px">
+                      <el-form-item label="规则">
+                        <div v-for="(content, contentIndex) in item.content"
+                             :key="contentIndex"
+                             style="background-color: #fdfdfd;padding: 15px; border: 1px solid #e6e6e6; margin-bottom: 20px;">
+                          <el-row>
+                            <el-col :span="22">
+                              <el-form :model="content" label-width="70px">
+                                <!-- 第一天不显示时间选择 -->
+                                <el-form-item label="时间" prop="time" v-if="index > 0">
+                                  <el-time-picker
+                                    class="custom-input"
+                                    v-model="content.time"
+                                    value-format="HH:mm"
+                                    format="HH:mm"
+                                    :picker-options="{ selectableRange: '00:00:00 - 23:59:59' }"
+                                    placeholder="时间"
+                                    style="width: 100px;height: 20px;">
+                                  </el-time-picker>
+                                </el-form-item>
+
+
+                                <div v-for="(setList, setIndex) in content.setting"
+                                     :key="setIndex"
+                                     style="background-color: #fdfdfd; border: 1px solid #e6e6e6; margin-bottom: 20px;">
+                                  <el-row>
+                                    <el-col :span="22">
+                                      <el-form :model="setList" label-width="70px">
+                                        <el-form-item label="添加客服" prop="intervalTime" style="margin: 2%">
+                                          <el-input-number
+                                            v-model="setList.intervalTime"
+                                            :min="1"
+                                            :max="1440"
+                                            style="width:100;margin-top: 10px;"
+                                          >
+                                          </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>
+                                    </el-col>
+                                    <el-col :span="1" :offset="1">
+                                      <i class="el-icon-delete"
+                                         @click="delSetList(index,contentIndex,setIndex)"
+                                         style="margin-top: 20px;"
+                                         v-if="content.setting.length>1"></i>
+                                    </el-col>
+                                  </el-row>
+                                </div>
+                                <el-link type="primary"
+                                         class="el-icon-plus"
+                                         :underline="false"
+                                         @click='addSetList(contentIndex,item.content)'>添加内容
+                                </el-link>
+                              </el-form>
+                            </el-col>
+                            <el-col :span="1" :offset="1">
+                              <i class="el-icon-delete"
+                                 @click="delContent(index,contentIndex)"
+                                 style="margin-top: 20px;"
+                                 v-if="item.content.length>1"></i>
+                            </el-col>
+                          </el-row>
+                        </div>
+                        <el-link type="primary"
+                                 v-if="index > 0"
+                                 class="el-icon-plus"
+                                 :underline="false"
+                                 @click='addContent(index)'>添加规则
+                        </el-link>
+                      </el-form-item>
+                    </el-form>
+                  </div>
+                </el-col>
+                <el-col :span="1" :offset="1">
+                  <i class="el-icon-delete"
+                     @click="delSetting(index)"
+                     v-if="setting.length>1"></i>
+                </el-col>
+              </el-row>
+            </el-timeline-item>
+          </el-timeline>
+          <!--          <el-link type="primary"
+                             class="el-icon-plus"
+                             :underline="false"
+                             @click='addSetting()'>添加天数</el-link> -->
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer" style="float: right;">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {addSopTemp} from "@/api/qw/sopTemp";
+
+export default {
+  name: "AddAiChatTemp",
+  data() {
+    return {
+      // 状态字典
+      statusOptions: [],
+      // 添加推送方式字典
+      sysQwSopType: [],
+      // 表单数据
+      form: {
+        name: null,
+        setting: null,
+        status: "1",
+        sort: 1,
+        gap: 1,
+        sendType: 4, // AI对话类型,固定值
+      },
+      // 规则设置
+      setting: [],
+      // 表单校验
+      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'}
+        ]
+      }
+    };
+  },
+  created() {
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+    // 获取推送方式字典
+    this.getDicts("sys_qw_sop_type").then(response => {
+      this.sysQwSopType = response.data;
+    });
+    this.getList();
+  },
+  methods: {
+    getList() {
+      this.setting.push({
+        name: null,
+        content: [{
+          type: 1,
+          contentType: '1',
+          setting: [{
+            intervalTime: 5,
+            contentType: '1',
+            value: ""
+          }]
+        }]
+      });
+    },
+
+    addSetList(contentIndex, content) {
+      content[contentIndex].setting.push({
+        intervalTime: 5,
+        contentType: '1',
+        value: ""
+      });
+    },
+
+    delSetList(index, contentIndex, setIndex) {
+      this.setting[index].content[contentIndex].setting.splice(setIndex, 1);
+    },
+
+    addContent(index) {
+      this.setting[index].content.push({
+        type: 1,
+        contentType: '1',
+        // 默认5分钟
+        setting: [{
+          intervalTime: 5,
+          contentType: '1',
+          value: ""
+        }]
+      });
+    },
+
+    delContent(index, contentIndex) {
+      this.setting[index].content.splice(contentIndex, 1);
+    },
+
+    addSetting() {
+      this.setting.push({
+        name: null,
+        content: [{
+          type: 1,
+          contentType: '1',
+          setting: [{
+            intervalTime: 5, // 默认5分钟
+            contentType: '1',
+            value: ""
+          }]
+        }]
+      });
+    },
+
+    delSetting(index) {
+      this.setting.splice(index, 1);
+    },
+
+    submitForm() {
+      this.$refs["formRef"].validate(valid => {
+        if (valid) {
+          // 验证内容
+          for (let day of this.setting) {
+            for (let content of day.content) {
+              if (content.time === undefined && this.setting.indexOf(day) > 0) {
+                return this.$message.error("时间不能为空");
+              }
+              for (let set of content.setting) {
+                if (!set.intervalTime) {
+                  return this.$message.error("间隔时间不能为空");
+                }
+                if (!set.value) {
+                  return this.$message.error("内容不能为空");
+                }
+              }
+            }
+          }
+
+          this.form.setting = JSON.stringify(this.setting);
+          addSopTemp(this.form).then(response => {
+            this.$message.success("新增成功");
+            window.location.replace('/qw/conversion/sopTemp')
+            this.reset();
+          });
+        }
+      });
+    },
+
+    cancel() {
+      this.$router.push('/qw/conversion/sopTemp');
+    }
+  }
+};
+</script>
+
+<style scoped>
+.custom-input /deep/ .el-input__inner {
+  height: 20px;
+  text-align: center;
+}
+
+.custom-input /deep/ .el-input__icon {
+  line-height: 10px;
+}
+
+.tip {
+  color: #909399;
+  font-size: 12px;
+  margin-left: 10px;
+}
+</style>

+ 981 - 0
src/views/qw/sopTemp/addSopTemp.vue

@@ -0,0 +1,981 @@
+<template>
+  <div class="app-container">
+
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 "> sop规则【企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 "> sop规则【AI插件】模板</div>
+
+    <div style="margin-top: 10px;margin-left: 50px;margin-right: 100px;margin-bottom: 60px;">
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <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="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="规则" prop="setting">
+          <el-timeline >
+            <el-timeline-item :timestamp="'第'+(1+(form.gap*index))+'天'" :color="'#0bbd87'" placement="top" v-for="(item, index) in setting"  style="margin-top: 10px;">
+              <el-row>
+                <el-col :span="22">
+                  <div style="background-color: #fbfbfb;padding: 15px;  border: 1px solid #e6e6e6; margin-bottom: 20px;">
+                    <el-form :model="item"  label-width="80px">
+
+                      <el-form-item label="内容名称"  style="height: 50px;">
+                        <el-input v-model="item.name" placeholder="内容名称,仅内部可见" />
+                      </el-form-item>
+
+                      <el-form-item label="规则"  >
+                        <div v-for="(content, contentIndex) in item.content" style="background-color: #fdfdfd;padding: 15px;  border: 1px solid #e6e6e6; margin-bottom: 20px;">
+
+                          <el-row>
+
+                            <el-col el-col :span="22" >
+                              <el-form :model="content" label-width="70px">
+                                <el-form-item label="时间"  prop="time"  >
+                                  <el-time-picker
+                                    class="custom-input"
+                                    v-model="content.time"
+                                    value-format="HH:mm"
+                                    format="HH:mm"
+                                    :picker-options="{ selectableRange: '00:21:00 - 23:59:59' }"
+                                    placeholder="时间"
+                                    style="width: 100px;height: 20px;" >
+                                  </el-time-picker>
+                                </el-form-item>
+                                <el-form-item label="消息类别"  >
+                                  <el-radio-group v-model="content.type" @change="() => content.contentType = '1'" >
+                                    <el-radio
+                                      :label="1"
+
+                                    >普通</el-radio>
+                                    <el-radio
+                                      :label="2"
+
+                                    >课程</el-radio>
+                                    <el-radio
+                                      :label="3"
+
+                                    >订单</el-radio>
+                                    <el-radio
+                                      :label="4"
+
+                                    >AI触达</el-radio>
+                                  </el-radio-group>
+                                </el-form-item>
+
+                                <el-form-item label="课程"  v-if="content.type == 2 ">
+                                  <el-select  v-model="content.courseId" placeholder="请选择课程" style=" margin-right: 10px;" size="mini"  @change="courseChange(content,index,contentIndex)">
+                                    <el-option
+                                      v-for="dict in courseList"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="parseInt(dict.dictValue)"
+                                    />
+                                  </el-select>
+                                  <el-select  v-model="content.videoId" placeholder="请选择小节" size="mini" style=" margin-right: 10px;" @change="videoIdChange(content,index,contentIndex)" >
+                                    <el-option
+                                      v-for="dict in videoList[index][contentIndex]"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="parseInt(dict.dictValue)"
+                                    />
+                                  </el-select>
+                                  <el-select  v-model="content.courseType" placeholder="请选择消息类型" size="mini" style=" margin-right: 10px;" v-if="content.type != 4 ">
+                                    <el-option
+                                      v-for="dict in sysFsSopWatchStatus"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="parseInt(dict.dictValue)"
+                                    />
+                                  </el-select>
+
+
+
+
+                                </el-form-item>
+                                <el-form-item label="Ai触达"  v-if="content.type == 4 ">
+                                  <el-select  v-model="content.aiTouch" placeholder="请选择Ai触达类型" size="mini" style=" margin-right: 10px;" v-if="content.type == 4 ">
+                                    <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="内容类型"  >-->
+                                <div v-for="(setList, setIndex) in content.setting" :key="setIndex" style="background-color: #fdfdfd; border: 1px solid #e6e6e6; margin-bottom: 20px;" v-if="content.type != 4 ">
+                                  <el-row>
+                                    <el-col :span="22">
+                                      <el-form :model="setList" label-width="70px">
+                                        <el-form-item label="内容类别" style="margin: 2%">
+                                          <div v-if="form.sendType == 1 ">
+                                            <el-radio-group  v-model="setList.contentType" >
+                                              <el-radio
+                                                v-for="item in sysQwSopContentType"
+                                                :label="item.dictValue"
+                                                :disabled="item.dictValue === '1' && content.setting.some(s => s.contentType == '1')">
+                                                {{ item.dictLabel }}
+                                              </el-radio>
+                                            </el-radio-group>
+                                          </div>
+                                          <div v-if="form.sendType == 2 ">
+                                            <!--                                              <div v-if="content.type == 2 ">-->
+                                            <!--                                                <el-radio-group  v-model="setList.contentType" @change="handleContentTypeChange(setList,index,setIndex)">-->
+                                            <!--                                                  <el-radio  :label="item.dictValue" v-for="item in sysQwSopSettingType">{{item.dictLabel}}</el-radio>-->
+                                            <!--                                                </el-radio-group>-->
+                                            <!--                                              </div>-->
+                                            <!--                                              <div v-else>-->
+                                            <el-radio-group  v-model="setList.contentType" @change="handleContentTypeChange(content,index,contentIndex,setIndex)">
+                                              <el-radio   :label="item.dictValue" v-for="item in sysQwSopAiContentType">{{item.dictLabel}}</el-radio>
+                                            </el-radio-group>
+                                            <!--                                              </div>-->
+
+                                          </div>
+                                        </el-form-item>
+                                        <el-form-item label="内容"  >
+                                          <el-input v-if="setList.contentType == 1 " v-model="setList.value" type="textarea" :rows="3" placeholder="内容" style="width: 90%;margin-top: 10px;"/>
+
+                                          <ImageUpload v-if="setList.contentType == 2 " v-model="setList.imgUrl" type="image" :num="1"  :width="150" :height="150" />
+
+                                          <div v-if="setList.contentType == 3 ">
+                                            <el-card class="box-card">
+                                              <el-form-item label="链接标题:"  label-width="100px">
+                                                <el-input v-model="setList.linkTitle" placeholder="请输入链接标题" style="width: 90%;"/>
+                                              </el-form-item>
+                                              <el-form-item label="链接描述:"   label-width="100px" >
+                                                <el-input type="textarea" :rows="3" v-model="setList.linkDescribe" placeholder="请输入链接描述" style="width: 90%;margin-top: 1%;"/>
+                                              </el-form-item>
+                                              <el-form-item label="链接封面:"   label-width="100px">
+                                                <ImageUpload v-model="setList.linkImageUrl" type="image" :num="1" :file-size="2" :width="150" :height="150" style="margin-top: 1%;" />
+                                              </el-form-item>
+                                              <div v-if="content.type!=2" style="margin-top: 1%">
+                                                <el-form-item label="链接地址:"  label-width="100px" >
+                                                  <el-input v-model="setList.linkUrl" placeholder="请输入链接地址" style="width: 90%;"/>
+                                                </el-form-item>
+                                              </div>
+                                              <div v-if="content.type==2">
+                                                <el-form-item label="链接地址:"  label-width="100px" >
+                                                  <el-tag type="warning" v-model="setList.isBindUrl=1">选择的课程小节 即为卡片链接地址</el-tag>
+                                                </el-form-item>
+                                              </div>
+                                            </el-card>
+                                          </div>
+
+                                          <div v-if="setList.contentType == 4">
+                                            <el-card class="box-card">
+                                              <el-form-item label="标题" prop="miniprogramTitle">
+                                                <el-input v-model="setList.miniprogramTitle" placeholder="请输入小程序消息标题,最长为64字"  />
+                                              </el-form-item>
+                                              <el-form-item label="封面" prop="miniprogramPicUrl">
+                                                <ImageUpload v-model="setList.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="setList.miniprogramAppid='wx73f85f8d62769119' " 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="setList.miniprogramPage" placeholder="小程序消息打开后的路径" disabled />
+                                              </el-form-item>
+                                            </el-card>
+                                          </div>
+
+                                          <div v-if="setList.contentType == 5 ">
+
+                                            <el-form-item label="上传文件:" prop="fileUrl" label-width="100px">
+                                              <el-upload
+                                                v-model="setList.fileUrl"
+                                                class="avatar-uploader"
+                                                :action="uploadUrl"
+                                                :show-file-list="false"
+                                                :on-success="(res, file) => handleAvatarSuccessFile(res, file, setList)"
+                                                :before-upload="beforeAvatarUploadFile">
+                                                <i class="el-icon-plus avatar-uploader-icon"></i>
+                                              </el-upload>
+                                              <el-link v-if="setList.fileUrl" type="primary" :href="downloadUrl(setList.fileUrl)" download>
+                                                {{setList.fileUrl}}
+                                              </el-link>
+                                            </el-form-item>
+
+                                          </div>
+
+                                          <div v-if="setList.contentType == 6 ">
+                                            <el-form-item label="上传视频:" prop="videoUrl" label-width="100px">
+                                              <el-upload
+                                                v-model="setList.videoUrl"
+                                                class="avatar-uploader"
+                                                :action="uploadUrl"
+                                                :show-file-list="false"
+                                                :on-success="(res, file) => handleAvatarSuccessVideo(res, file, setList)"
+                                                :before-upload="beforeAvatarUploadVideo">
+                                                <i class="el-icon-plus avatar-uploader-icon"></i>
+                                              </el-upload>
+                                              <video v-if="setList.videoUrl"
+                                                     :src="setList.videoUrl"
+                                                     controls style="width: 200px;height: 100px">
+                                              </video>
+                                            </el-form-item>
+                                          </div>
+                                          <div v-if="setList.contentType == 7 ">
+                                              <el-input
+                                                        v-model="setList.value"
+                                                        type="textarea" :rows="3" maxlength="66" show-word-limit
+                                                        placeholder="输入要转为语音的内容" style="width: 90%;margin-top: 10px;"
+                                                        @input="handleInputVideoText(setList.value,setList)"/>
+                                          </div>
+                                          <div v-if="setList.contentType == 8 ">
+                                            <el-button type="primary" style="margin-bottom: 1%" @click="hanldeSelectVideoNum(content,index,contentIndex,setIndex)">选择视频号</el-button>
+                                            <el-card class="box-card" v-if="setList.coverUrl">
+                                              <el-form-item label="封面标题:"  label-width="100px">
+                                                <el-input v-model="setList.nickname" style="width: 90%;margin-bottom: 1%" disabled/>
+                                              </el-form-item>
+                                              <el-form-item label="头像:"   label-width="100px" >
+                                                <el-image
+                                                  v-if="setList.avatar != null"
+                                                  :src="setList.avatar"
+                                                  :preview-src-list="[setList.avatar]"
+                                                  :style="{ width: '50px', height: '50px' }"
+                                                ></el-image>
+                                              </el-form-item>
+                                              <el-form-item label="封面:"   label-width="100px" >
+                                                <el-image
+                                                  v-if="setList.coverUrl != null"
+                                                  :src="setList.coverUrl"
+                                                  :preview-src-list="[setList.coverUrl]"
+                                                  :style="{ width: '200px', height: '200px' }"
+                                                ></el-image>
+
+                                              </el-form-item>
+                                              <el-form-item label="简介:"   label-width="100px" >
+                                                <el-input type="textarea" :rows="3" v-model="setList.desc"  style="width: 90%;margin-top: 1%;" disabled />
+                                              </el-form-item>
+                                              <el-form-item label="视频地址:"  label-width="100px" style="margin-top: 1%" >
+                                                <el-input v-model="setList.url" style="width: 90%;" disabled />
+                                              </el-form-item >
+
+                                            </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
+                                              v-model="setList.isBindUrl"
+                                              :disabled="!content.videoId"
+                                              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 != 2  && setList.contentType != 5  && setList.contentType != 6 && setList.contentType != 8 " style="margin-top: 1%">
+                                          <el-row>
+                                            <el-input-number  v-model="setList.expiresDays"  :min="0" :max="100" ></el-input-number>
+                                            (天)
+                                          </el-row>
+                                          <el-row>
+                                            <span class="tip">填写0或不填时,默认为系统配置的默认时间</span>
+                                          </el-row>
+                                        </el-form-item>
+
+                                      </el-form>
+                                    </el-col>
+                                    <el-col :span="1" :offset="1">
+                                      <i class="el-icon-delete" @click="delSetList(index,contentIndex,setIndex)" style="margin-top: 20px;" v-if="content.setting.length>1"></i>
+                                    </el-col>
+                                  </el-row>
+
+                                </div>
+                                <el-link type="primary" class="el-icon-plus" :underline="false" @click='addSetList(contentIndex,item.content)' v-if="content.type != 4 ">添加内容</el-link>
+                                <!--                                </el-form-item>-->
+
+                              </el-form>
+
+                            </el-col>
+                            <el-col :span="1" :offset="1">
+                              <i class="el-icon-delete" @click="delContent(index,contentIndex)" style="margin-top: 20px;" v-if="item.content.length>1"></i>
+                            </el-col>
+                          </el-row>
+                        </div>
+                        <el-link type="primary" class="el-icon-plus" :underline="false" @click='addContent(index)'>添加规则</el-link>
+                      </el-form-item>
+                    </el-form>
+                  </div>
+                </el-col>
+                <el-col :span="1" :offset="1">
+                  <i class="el-icon-delete-solid" @click="delSetting(index)"  v-if="setting.length>1"></i>
+                </el-col>
+              </el-row>
+            </el-timeline-item>
+
+
+          </el-timeline>
+          <el-link type="primary" class="el-icon-plus" :underline="false" @click='addSetting()'>添加天数</el-link>
+        </el-form-item>
+
+      </el-form>
+      <div slot="footer" class="dialog-footer" style="float: right;">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </div>
+
+
+    <el-dialog :title="videoNumOptions.title" :visible.sync="videoNumOptions.open" style="width: 1500px;height: 100%" append-to-body>
+      <userVideo ref="QwUserVideo" @videoResult="qwUserVideoResult" ></userVideo>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import {addSopTemp, delSopTemp, exportSopTemp, getSopTemp, listSopTemp, updateSopTemp} from "@/api/qw/sopTemp";
+import {courseList, videoList} from "@/api/qw/sop";
+import ImageUpload from "@/views/qw/sop/ImageUpload";
+import FriendMaterial from "@/views/qw/friendMaterial/index.vue";
+import userVideo from "@/views/qw/userVideo/userVideo.vue";
+
+export default {
+  name: "addSopTemp",
+  components: { ImageUpload,FriendMaterial,userVideo},
+  data() {
+    return {
+
+      //图片放大
+      dialogVisible: false,
+      dialogImageUrl:null,
+
+      uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS2",
+      uploadUrlByVoice:process.env.VUE_APP_BASE_API+"/common/uploadOSSByHOOKVoice",
+      //上传语音的遮罩层
+      voiceLoading :false,
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      sysFsSopWatchStatus: [],
+      //消息内容类型 企微版
+      sysQwSopContentType:[],
+      //插件版
+      sysQwSopAiContentType:[],
+      //链接版类别
+      sysQwSopSettingType:[],
+
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // sop模板表格数据
+      setting: [],
+      courseList:[],
+      videoList:[],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 状态字典
+      statusOptions: [],
+      // 查询参数
+      form: {
+        name: null,
+        setting: null,
+        status: "1",
+        sort: 1,
+        companyId: null,
+        gap:1,
+      },
+      videoNumOptions:{
+        title:'选择视频号',
+        open:false,
+        content:null,
+        contentIndex:null,
+        setIndex:null,
+      },
+      // 表单校验
+      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' }
+        ],
+      },
+      contentRules:{
+        time:[{ required: true, message: '时间不能为空', trigger: 'blur' }],
+      }
+    };
+  },
+  created() {
+
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+    courseList().then(response => {
+      this.courseList = response.list;
+    });
+    this.getDicts("sys_fs_sop_watch_status").then(response => {
+      this.sysFsSopWatchStatus = response.data;
+    });
+
+    this.getDicts("sys_qwSopAi_contentType").then(response => {
+      this.sysQwSopAiContentType = response.data;
+    });
+
+    this.getDicts("sys_qwSop_contentType").then(response => {
+      this.sysQwSopContentType = response.data;
+    });
+    this.getDicts("sys_qwSop_settingType").then(response => {
+      this.sysQwSopSettingType = response.data;
+    });
+
+
+    this.form.sendType = this.$route.params && this.$route.params.command;
+
+    setTimeout(() => {
+      this.getList();
+    }, 200);
+
+  },
+  methods: {
+    handleInputVideoText(value,content){
+      // 允许的字符:中文、英文(大小写)、数字和指定标点符号(,。!?)
+      const regex = /^[\u4e00-\u9fa5,。!?,!?]+$/;
+
+      // 删除不符合条件的字符
+      const filteredValue = value.split('').filter(char => regex.test(char)).join('');
+
+      this.$set(content, 'value', filteredValue);
+
+    },
+    addSetList(contentIndex, content) {
+
+      if (this.form.sendType == 1) {
+        for (let i = 0; i < content.length; i++) {
+
+          if (content[i].setting.length > 9) {
+            return this.$message.error("因为企微接口限制,企微模板一条消息只能设置最多~9个内容!!")
+          } else {
+
+            // 动态生成新的设置项
+            const newSetting = content[i].setting.some(item => item.contentType === '1')
+              ? {contentType: '2', imgUrl: ''}  // 如果存在 contentType = '1',添加 contentType = '2'
+              : {contentType: '1', value: ''};  // 如果不存在 contentType = '1',添加 contentType = '1'
+
+            // 将新设置项添加到 content[i].setting 数组中
+            content[i].setting.push(newSetting);
+          }
+
+        }
+      } else {
+
+        const newSetting = {
+          contentType: '1',
+          value: '',
+        };
+
+        // 将新设置项添加到 content.setting 数组中
+        content[contentIndex].setting.push(newSetting);
+
+      }
+
+    },
+    delSetList(index, contentIndex, setIndex) {
+      this.setting[index].content[contentIndex].setting.splice(setIndex, 1)
+    },
+
+    addContent(index) {
+
+      if (this.setting[index].content.length > 0 && this.form.sendType == 1) {
+        return this.$message.error("因为企微接口限制,企微模板一天只能设置一条消息~")
+      } else {
+        this.setting[index].content.push({type: 1, contentType: '1', setting: [{contentType: '1', value: "",}]})
+        this.videoList[index].push([])
+      }
+
+    },
+
+    delContent(index, contentIndex) {
+      this.setting[index].content.splice(contentIndex, 1)
+      this.videoList[index].splice(contentIndex, 1)
+    },
+    addSetting() {
+
+      this.setting.push({name: null, content: [{type: 1, contentType: '1', setting: [{contentType: '1', value: "",}]}]})
+      this.videoList.push([])
+
+    },
+    delSetting(index) {
+      this.setting.splice(index, 1)
+      this.videoList.splice(index, 1)
+    },
+    //上传文件
+    handleAvatarSuccessFile(res, file, content) {
+      if (res.code === 200) {
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'fileUrl', res.url);
+      } else {
+        this.msgError(res.msg);
+      }
+    },
+    beforeAvatarUploadFile(file) {
+      const isLt1M = file.size / 1024 / 1024 < 10;
+      if (!isLt1M) {
+        this.$message.error('上传大小不能超过 10MB!');
+      }
+      return isLt1M;
+    },
+
+    //下载文件
+    downloadUrl(materialUrl) {
+      // 直接返回文件 URL
+      return materialUrl;
+    },
+
+    //选择视频号
+    hanldeSelectVideoNum(content,index,contentIndex,setIndex){
+      this.videoNumOptions.content = content;
+      this.videoNumOptions.contentIndex = contentIndex;
+      this.videoNumOptions.setIndex = setIndex;
+        this.videoNumOptions.open=true;
+    },
+
+    qwUserVideoResult(val){
+
+      // 根据选中的内容,将返回的数据更新到相应的表单项
+      const content = this.videoNumOptions.content;
+      const setList = content.setting[this.videoNumOptions.setIndex];
+
+      setList.nickname = val.nickname;
+      setList.avatar = val.avatar;
+      setList.coverUrl = val.coverUrl;
+      setList.thumbUrl = val.thumbUrl;
+      setList.desc = val.desc;
+      setList.url = val.url;
+      setList.extras = val.extras;
+
+      this.videoNumOptions.open=false;
+    },
+
+    handleAvatarSuccessVideo(res, file, content) {
+      if (res.code == 200) {
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'videoUrl', res.url);
+      } else {
+        this.msgError(res.msg);
+      }
+    },
+    handleAvatarSuccessVoice(res, file, content) {
+      if (res.code == 200) {
+
+        // 创建 Audio 对象加载音频
+        const audio = new Audio(res.mp3Url);
+        audio.addEventListener('loadedmetadata', () => {
+          // 获取音频时长
+          this.$set(content, 'voiceDuration', Math.ceil(audio.duration));
+        });
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'voiceUrl', res.silkUrl);
+        this.$set(content, 'mp3Url', res.mp3Url);
+      } else {
+        this.msgError(res.msg);
+      }
+      this.voiceLoading = false;
+    },
+    beforeAvatarUploadVideo(file) {
+      const isLt30M = file.size / 1024 / 1024 < 10;
+      const isMP4 = file.type === 'video/mp4';
+
+      if (!isMP4) {
+        this.$message.error('仅支持上传 MP4 格式的视频文件!');
+        return false;
+      }
+
+      if (!isLt30M) {
+        this.$message.error('上传大小不能超过 10MB!');
+        return false;
+      }
+
+      return true;
+    },
+    beforeAvatarUploadVoice(file) {
+      return new Promise((resolve, reject) => {
+        const isLt10M = file.size / 1024 / 1024 < 10; // 假设语音文件大小限制为10MB
+        const isVoiceType = ['audio/mp3', 'audio/mpeg', 'audio/wav', 'audio/x-wav'].includes(file.type);
+
+        if (!isVoiceType) {
+          this.$message.error('仅支持上传 MP3, WAV, X-WAV 格式的语音文件!');
+          return reject(false); // 不允许继续上传
+        }
+
+        if (!isLt10M) {
+          this.$message.error('上传大小不能超过 10MB!');
+          return reject(false); // 不允许继续上传
+        }
+
+        // 使用 FileReader 读取文件
+        const reader = new FileReader();
+        reader.onload = (event) => {
+          const audio = new Audio(event.target.result);
+          audio.addEventListener('loadedmetadata', () => {
+            // 获取时长并保存
+            if (Math.ceil(audio.duration) > 30) {
+              this.$message.error('音频时长不能超过30秒!');
+              this.voiceLoading = false;
+              return reject(false); // 不允许继续上传
+            }
+            resolve(true); // 允许上传
+          });
+        };
+
+        reader.onerror = () => {
+          this.$message.error('无法读取音频文件!请上传正确的音频文件');
+          reject(false); // 不允许继续上传
+        };
+        this.voiceLoading = true;
+        reader.readAsDataURL(file); // 开始读取文件
+      });
+    },
+
+    handleClosegroupUser(list) {
+      const index = this.userSelectList.findIndex(t => t === list);
+      if (index !== -1) {
+        this.userSelectList.splice(index, 1);
+      }
+    },
+    courseChange(content, index, countIndex) {
+      this.$set(content, 'videoId', null);
+      let selectedCourse;
+      if (content.courseId != null) {
+        // 查找选中的课程对应的 label
+        selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
+      }
+
+      for (let i = 0; i < content.setting.length; i++) {
+        //如果是链接的才上
+        if (selectedCourse && content.type == 2 && content.courseId != null) {
+
+          if (content.setting[i].contentType == 3){
+            //响应式直接给链接的标题/封面上值
+              this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
+              this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+          }
+
+          if (content.setting[i].contentType == 4){
+            this.$set(content.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+          }
+
+
+        }
+      }
+      videoList(content.courseId).then(response => {
+
+        this.videoList[index].splice(countIndex, 1, response.list);
+
+      });
+    },
+
+    handleContentTypeChange(content, index, countIndex, setIndex) {
+
+      //如果是链接的才上
+      if (content.courseId != null && content.type == 2) {
+        const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
+        for (let i = 0; i < content.setting.length; i++) {
+          //响应式直接给链接的标题/封面上值
+          if (selectedCourse  && content.type == 2 && content.courseId != null) {
+
+            if (content.setting[i].contentType == 3){
+              this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
+              this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+            }
+            if (this.setting[i].contentType == 4){
+              this.$set(content.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+            }
+
+          }
+
+        }
+
+      }
+      if (content.videoId != null && content.type == 2) {
+        // 查找选中的课节对应的 label
+        const selectedVideo = this.videoList[index][countIndex].find(course => parseInt(course.dictValue) === content.videoId);
+
+        for (let i = 0; i < content.setting.length; i++) {
+          //响应式直接给链接的描述上值
+          if (selectedVideo  && content.type == 2 && content.videoId != null) {
+            if (content.setting[i].contentType == 3){
+              this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+            }
+            if (this.setting[i].contentType == 4){
+              this.$set(content.setting[i], 'miniprogramTitle', selectedVideo.dictLabel);
+            }
+          }
+        }
+      }
+
+
+    },
+    videoIdChange(content, index, countIndex) {
+      //选择了课程小节则 默认绑上
+
+      let selectedVideo;
+      // 查找选中的课程对应的 label
+      if (content.videoId != null) {
+        selectedVideo = this.videoList[index][countIndex].find(course => parseInt(course.dictValue) === content.videoId);
+      }
+      for (let i = 0; i < content.setting.length; i++) {
+        //课程消息-文本内容
+        if (content.setting[i].contentType == 1 && content.type == 2) {
+          this.$set(content.setting[i], 'isBindUrl', '1');
+        }
+        //如果是链接的才上
+        if (selectedVideo && content.type == 2 && content.videoId != null) {
+          //响应式直接给链接的描述上值
+          if (content.setting[i].contentType == 3 ){
+
+              this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+          }
+          if (content.setting[i].contentType == 4){
+              this.$set(content.setting[i], 'miniprogramTitle', selectedVideo.dictLabel);
+          }
+
+        }
+      }
+
+    },
+    /** 查询sop模板列表 */
+    getList() {
+      this.loading = true;
+      listSopTemp(this.queryParams).then(response => {
+        this.sopTempList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.$store.dispatch("tagsView/delView", this.$route);
+      this.$router.replace('/qw/conversion/sopTemp')
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        setting: null,
+        status: "0",
+        sort: null,
+        createTime: null,
+        createBy: null,
+        companyId: 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 = "添加sop模板";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getSopTemp(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改sop模板";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+
+        if (valid) {
+          this.form.setting = JSON.stringify(this.setting)
+
+          if (this.setting.length <= 0) {
+            return this.$message("请添加规则")
+          }
+
+          for (let i = 0; i < this.setting.length; i++) {
+
+
+            for (let j = 0; j < this.setting[i].content.length; j++) {
+
+              if (this.setting[i].name == null || this.setting[i].name == "") {
+                return this.$message.error("内容名称不能为空")
+              }
+              if (this.setting[i].content[j].time == null || this.setting[i].content[j].time == "") {
+                return this.$message.error("时间不能为空")
+              }
+              if (this.setting[i].content[j].type != 4) {
+                for (let k = 0; k < this.setting[i].content[j].setting.length; k++) {
+                  if (this.setting[i].content[j].setting[k].contentType == 1 && (this.setting[i].content[j].setting[k].value == null || this.setting[i].content[j].setting[k].value == "")) {
+                    return this.$message.error("内容不能为空")
+                  }
+                  if (this.setting[i].content[j].setting[k].contentType == 2 && (this.setting[i].content[j].setting[k].imgUrl == null || this.setting[i].content[j].setting[k].imgUrl == "")) {
+                    return this.$message.error("图片不能为空")
+                  }
+                  if (this.setting[i].content[j].setting[k].contentType == 3 && (this.setting[i].content[j].setting[k].linkTitle == null || this.setting[i].content[j].setting[k].linkTitle == "")) {
+                    return this.$message.error("链接标题不能为空")
+                  }
+                  if (this.setting[i].content[j].setting[k].contentType == 3 && (this.setting[i].content[j].setting[k].linkDescribe == null || this.setting[i].content[j].setting[k].linkDescribe == "")) {
+                    return this.$message.error("链接描述不能为空")
+                  }
+                  if (this.setting[i].content[j].setting[k].contentType == 3 && (this.setting[i].content[j].setting[k].linkImageUrl == null || this.setting[i].content[j].setting[k].linkImageUrl == "")) {
+                    return this.$message.error("链接图片不能为空")
+                  }
+                  if (this.setting[i].content[j].setting[k].contentType == 3 && this.setting[i].content[j].setting[k].type == 1 && (this.setting[i].content[j].setting[k].linkUrl == null || this.setting[i].content[j].setting[k].linkUrl == "")) {
+                    return this.$message.error("链接地址不能为空")
+                  }
+
+                  if (this.setting[i].content[j].setting[k].contentType == 4 && (this.setting[i].content[j].setting[k].miniprogramTitle == null || this.setting[i].content[j].setting[k].miniprogramTitle == "")) {
+                    return this.$message.error("小程序消息标题不能为空")
+                  }
+                  if (this.setting[i].content[j].setting[k].contentType == 4 && (this.setting[i].content[j].setting[k].miniprogramPicUrl == null || this.setting[i].content[j].setting[k].miniprogramPicUrl == "")) {
+                    return this.$message.error("小程序封面地址不能为空")
+                  }
+                  if (this.setting[i].content[j].setting[k].contentType == 5 && (this.setting[i].content[j].setting[k].fileUrl == null || this.setting[i].content[j].setting[k].fileUrl == "")) {
+                    return this.$message.error("文件不能为空")
+                  }
+                  if (this.setting[i].content[j].setting[k].contentType == 6 && (this.setting[i].content[j].setting[k].videoUrl == null || this.setting[i].content[j].setting[k].videoUrl == "")) {
+                    return this.$message.error("视频不能为空")
+                  }
+                  if (this.setting[i].content[j].setting[k].contentType == 7 && (this.setting[i].content[j].setting[k].value == null || this.setting[i].content[j].setting[k].value == "")) {
+                    return this.$message.error("语音文本不能为空")
+                  }
+                  if (this.setting[i].content[j].setting[k].contentType == 8 && (this.setting[i].content[j].setting[k].url == null || this.setting[i].content[j].setting[k].url == "")) {
+                    return this.$message.error("视频号信息不能为空")
+                  }
+                }
+              } else {
+                if (this.setting[i].content[j].aiTouch == null || this.setting[i].content[j].aiTouch == '') {
+                  return this.$message.error("AI触达不能为空")
+                }
+              }
+            }
+
+
+          }
+
+          if (this.form.id != null) {
+            updateSopTemp(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.$store.dispatch("tagsView/delView", this.$route);
+              // this.$router.replace('/qw/conversion/sopTemp')
+              window.location.replace('/qw/conversion/sopTemp')
+              this.reset();
+
+            });
+          } else {
+
+            addSopTemp(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.$store.dispatch("tagsView/delView", this.$route);
+              // this.$router.replace('/qw/conversion/sopTemp')
+              window.location.replace('/qw/conversion/sopTemp')
+              this.reset();
+
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除sop模板编号为"' + ids + '"的数据项?', "警告", {
+        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(() => {
+      });
+    }
+  }
+};
+</script>
+
+<style scoped>
+.custom-input /deep/ .el-input__inner {
+  height: 20px;
+  text-align: center;
+}
+
+.custom-input /deep/ .el-input__icon {
+  line-height: 10px;
+}
+</style>

+ 734 - 0
src/views/qw/sopTemp/addSopTempOld.vue

@@ -0,0 +1,734 @@
+<template>
+  <div class="app-container">
+
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 "> sop规则【企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 "> sop规则【AI插件】模板</div>
+
+    <div style="margin-top: 10px;margin-left: 50px;margin-right: 100px;margin-bottom: 60px;">
+      <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="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="规则" prop="setting">
+          <el-timeline >
+            <el-timeline-item :timestamp="'第'+(1+(form.gap*index))+'天'" :color="'#0bbd87'" placement="top" v-for="(item, index) in setting"  style="margin-top: 10px;">
+            <el-row>
+              <el-col :span="22">
+                <div style="background-color: #fbfbfb;padding: 15px;  border: 1px solid #e6e6e6; margin-bottom: 20px;">
+                  <el-form :model="item"  label-width="80px">
+
+                    <el-form-item label="内容名称"  style="height: 50px;">
+                       <el-input v-model="item.name" placeholder="内容名称,仅内部可见" />
+                    </el-form-item>
+
+                    <el-form-item label="规则"  >
+                       <div v-for="(content, contentIndex) in item.content" style="background-color: #fdfdfd;padding: 15px;  border: 1px solid #e6e6e6; margin-bottom: 20px;">
+
+                         <el-row>
+
+                           <el-col el-col :span="22" >
+                              <el-form :model="content" label-width="70px">
+                                <el-form-item label="时间"  prop="time"  >
+                                   <el-time-picker
+                                     class="custom-input"
+                                     v-model="content.time"
+                                     value-format="HH:mm"
+                                     format="HH:mm"
+                                     :picker-options="{ selectableRange: '00:00:00 - 23:59:59' }"
+                                     placeholder="时间"
+                                     style="width: 100px;height: 20px;" >
+                                   </el-time-picker>
+                                </el-form-item>
+                                <el-form-item label="消息类别"  >
+                                   <el-radio-group v-model="content.type" @change="() => content.contentType = '1'" >
+                                       <el-radio
+                                         :label="1"
+
+                                       >普通</el-radio>
+                                       <el-radio
+                                         :label="2"
+
+                                       >课程</el-radio>
+                                       <el-radio
+                                         :label="3"
+
+                                       >订单</el-radio>
+                                   </el-radio-group>
+                                </el-form-item>
+
+                                <el-form-item label="课程"  v-if="content.type == 2 ">
+                                   <el-select  v-model="content.courseId" placeholder="请选择课程" style=" margin-right: 10px;" size="mini"  @change="courseChange(content,index,contentIndex)">
+                                     <el-option
+                                         v-for="dict in courseList"
+                                         :key="dict.dictValue"
+                                         :label="dict.dictLabel"
+                                         :value="parseInt(dict.dictValue)"
+                                       />
+                                   </el-select>
+                                   <el-select  v-model="content.videoId" placeholder="请选择小节" size="mini" style=" margin-right: 10px;" @change="videoIdChange(content,index,contentIndex)" >
+                                     <el-option
+                                         v-for="dict in videoList[index][contentIndex]"
+                                         :key="dict.dictValue"
+                                         :label="dict.dictLabel"
+                                         :value="parseInt(dict.dictValue)"
+                                       />
+                                   </el-select>
+                                   <el-select  v-model="content.courseType" placeholder="请选择消息类型" size="mini" style=" margin-right: 10px;">
+                                     <el-option
+                                         v-for="dict in sysFsSopWatchStatus"
+                                         :key="dict.dictValue"
+                                         :label="dict.dictLabel"
+                                         :value="parseInt(dict.dictValue)"
+                                       />
+                                   </el-select>
+
+                                </el-form-item>
+                                <el-form-item label="内容类别"  >
+
+<!--                                  <div v-for="(list, setIndex) in content.setting ">-->
+<!--                                    <el-row>-->
+<!--                                      <el-col el-col :span="22" >-->
+<!--                          -->
+<!--                                      </el-col>-->
+<!--                                      <el-col :span="1" :offset="1">-->
+<!--                                        <i class="el-icon-delete" @click="delContent(index,contentIndex)" style="margin-top: 20px;" v-if="item.content.length>1"></i>-->
+<!--                                      </el-col>-->
+<!--                                    </el-row>-->
+
+<!--                                  </div>-->
+
+                                  <div v-if="form.sendType == 1 ">
+                                    <el-radio-group  v-model="content.contentType" >
+                                      <el-radio  :label="item.dictValue" v-for="item in sysQwSopContentType">{{item.dictLabel}}</el-radio>
+                                    </el-radio-group>
+                                  </div>
+                                  <div v-if="form.sendType == 2 ">
+                                    <div v-if="content.type == 2 ">
+                                      <el-radio-group  v-model="content.contentType" @change="handleContentTypeChange(content,index,contentIndex)">
+                                        <el-radio  :label="item.dictValue" v-for="item in sysQwSopSettingType">{{item.dictLabel}}</el-radio>
+                                      </el-radio-group>
+                                    </div>
+                                    <div v-else>
+                                      <el-radio-group  v-model="content.contentType" @change="handleContentTypeChange(content,index,contentIndex)">
+                                        <el-radio   :label="item.dictValue" v-for="item in sysQwSopAiContentType">{{item.dictLabel}}</el-radio>
+                                      </el-radio-group>
+                                    </div>
+
+                                  </div>
+                                </el-form-item>
+                                <el-form-item label="内容"  >
+                                   <el-input v-if="content.contentType == 1 " v-model="content.value" type="textarea" :rows="3" placeholder="内容" style="width: 90%;margin-top: 10px;"/>
+
+                                    <ImageUpload v-if="content.contentType == 2 " v-model="content.imgUrl" type="image" :num="1" :width="150" :height="150" />
+
+                                    <div v-if="content.contentType == 3 ">
+                                      <el-card class="box-card">
+                                        <el-form-item label="链接标题:"  label-width="100px">
+                                          <el-input v-model="content.linkTitle" placeholder="请输入链接标题" style="width: 90%;"/>
+                                        </el-form-item>
+                                        <el-form-item label="链接描述:"   label-width="100px" >
+                                          <el-input type="textarea" :rows="3" v-model="content.linkDescribe" placeholder="请输入链接描述" style="width: 90%;margin-top: 1%;"/>
+                                        </el-form-item>
+                                        <el-form-item label="链接封面:"   label-width="100px">
+                                          <ImageUpload v-model="content.linkImageUrl" type="image" :num="1" :width="150" :height="150" style="margin-top: 1%;" />
+                                      </el-form-item>
+                                    <div v-if="content.type!=2" style="margin-top: 1%">
+                                    <el-form-item label="链接地址:"  label-width="100px" >
+                                      <el-input v-model="content.linkUrl" placeholder="请输入链接地址" style="width: 90%;"/>
+                                    </el-form-item>
+                                    </div>
+                                    <div v-if="content.type==2">
+                                    <el-form-item label="链接地址:"  label-width="100px" >
+                                      <el-tag type="warning" v-model="content.isBindUrl=1">选择的课程小节 即为卡片链接地址</el-tag>
+                                    </el-form-item>
+                                    </div>
+                                    </el-card>
+                                    </div>
+                                    <div v-if="content.contentType == 5 ">
+
+                                      <el-form-item label="上传文件:" prop="fileUrl" label-width="100px">
+                                        <el-upload
+                                        v-model="content.fileUrl"
+                                        class="avatar-uploader"
+                                        :action="uploadUrl"
+                                        :show-file-list="false"
+                                        :on-success="(res, file) => handleAvatarSuccessFile(res, file, content)"
+                                        :before-upload="beforeAvatarUploadFile">
+                                        <i class="el-icon-plus avatar-uploader-icon"></i>
+                                        </el-upload>
+                                        <el-link v-if="content.fileUrl" type="primary" :href="downloadUrl(content.fileUrl)" download>
+                                        {{content.fileUrl}}
+                                      </el-link>
+                                      </el-form-item>
+
+                                    </div>
+
+                                    <div v-if="content.contentType == 6 ">
+                                      <el-form-item label="上传视频:" prop="videoUrl" label-width="100px">
+                                        <el-upload
+                                        v-model="content.videoUrl"
+                                        class="avatar-uploader"
+                                        :action="uploadUrl"
+                                        :show-file-list="false"
+                                        :on-success="(res, file) => handleAvatarSuccessVideo(res, file, content)"
+                                        :before-upload="beforeAvatarUploadVideo">
+                                        <i class="el-icon-plus avatar-uploader-icon"></i>
+                                        </el-upload>
+                                        <video v-if="content.videoUrl"
+                                        :src="content.videoUrl"
+                                        controls style="width: 200px;height: 100px">
+                                        </video>
+                                      </el-form-item>
+                                    </div>
+                                    <div v-if="content.contentType == 7 ">
+                                      <el-form-item label="上传语音:" prop="videoUrl" label-width="100px">
+                                        <el-upload
+                                          v-model="content.voiceUrl"
+                                          class="avatar-uploader"
+                                          :action="uploadUrl"
+                                          :show-file-list="false"
+                                          :on-success="(res, file) => handleAvatarSuccessVoice(res, file, content)"
+                                          :before-upload="beforeAvatarUploadVoice">
+                                          <i class="el-icon-plus avatar-uploader-icon"></i>
+                                        </el-upload>
+                                        <audio  v-if="content.voiceUrl"
+                                               :src="content.voiceUrl"
+                                               controls style="width: 300px;height: 50px">
+                                        </audio >
+                                      </el-form-item>
+                                    </div>
+
+
+                                </el-form-item>
+
+                                <el-form-item label="添加短链" v-if="content.type == 2 && content.contentType == 1 "  >
+                                  <el-tooltip content="请先根据课程选定课程小节之后再添加" effect="dark" :disabled="!!content.videoId">
+                                    <el-switch
+                                      v-model="content.isBindUrl"
+                                      :disabled="!content.videoId"
+                                      active-color="#13ce66"
+                                      inactive-color="#DCDFE6"
+                                      active-value="1"
+                                      inactive-value="2">
+                                    </el-switch>
+                                  </el-tooltip>
+
+                                  <span v-if="content.isBindUrl == '1'" style="margin-left: 10px; color: #13ce66">添加URL</span>
+                                  <span v-if="content.isBindUrl == '2'" style="margin-left: 10px; color: #b1b4ba">不加URL</span>
+                                </el-form-item>
+
+                                <el-form-item label="课节过期时间" v-if="content.type == 2 && content.isBindUrl == '1' && content.contentType != 2  && content.contentType != 5  && content.contentType != 6 " style="margin-top: 1%">
+                                  <el-row>
+                                    <el-input-number  v-model="content.expiresDays"  :min="0" :max="100" ></el-input-number>
+                                    (天)
+                                  </el-row>
+                                  <el-row>
+                                    <span class="tip">填写0或不填时,默认为系统配置的默认时间</span>
+                                  </el-row>
+                                </el-form-item>
+
+                              </el-form>
+
+                           </el-col>
+                           <el-col :span="1" :offset="1">
+                             <i class="el-icon-delete" @click="delContent(index,contentIndex)" style="margin-top: 20px;" v-if="item.content.length>1"></i>
+                           </el-col>
+                         </el-row>
+                       </div>
+                       <el-link type="primary" class="el-icon-plus" :underline="false" @click='addContent(index)'>添加规则</el-link>
+                    </el-form-item>
+                  </el-form>
+                </div>
+              </el-col>
+              <el-col :span="1" :offset="1">
+                 <i class="el-icon-delete-solid" @click="delSetting(index)"  v-if="setting.length>1"></i>
+              </el-col>
+            </el-row>
+          </el-timeline-item>
+
+
+          </el-timeline>
+           <el-link type="primary" class="el-icon-plus" :underline="false" @click='addSetting()'>添加天数</el-link>
+        </el-form-item>
+
+      </el-form>
+      <div slot="footer" class="dialog-footer" style="float: right;">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { listSopTemp, getSopTemp, delSopTemp, addSopTemp, updateSopTemp, exportSopTemp } from "@/api/qw/sopTemp";
+import { courseList,videoList } from "@/api/qw/sop";
+import ImageUpload from "@/views/qw/sop/ImageUpload";
+import FriendMaterial from "@/views/qw/friendMaterial/index.vue";
+export default {
+  name: "SopTemp",
+      components: { ImageUpload,FriendMaterial},
+  data() {
+    return {
+      uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS",
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      sysFsSopWatchStatus: [],
+      //消息内容类型 企微版
+      sysQwSopContentType:[],
+      //插件版
+      sysQwSopAiContentType:[],
+      //类别
+      sysQwSopSettingType:[],
+      courseList:[],
+      videoList:[],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // sop模板表格数据
+      setting: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 状态字典
+      statusOptions: [],
+      // 查询参数
+      form: {
+        name: null,
+        setting: null,
+        status: "1",
+        sort: 1,
+        companyId: null,
+        gap:1,
+      },
+      // 表单校验
+      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' }
+        ],
+      },
+      contentRules:{
+        time:[{ required: true, message: '时间不能为空', trigger: 'blur' }],
+      }
+    };
+  },
+  created() {
+
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+    courseList().then(response => {
+      this.courseList = response.list;
+    });
+    this.getDicts("sys_fs_sop_watch_status").then(response => {
+      this.sysFsSopWatchStatus = response.data;
+    });
+
+    this.getDicts("sys_qwSopAi_contentType").then(response => {
+      this.sysQwSopAiContentType = response.data;
+    });
+
+    this.getDicts("sys_qwSop_contentType").then(response => {
+      this.sysQwSopContentType = response.data;
+    });
+    this.getDicts("sys_qwSop_settingType").then(response => {
+      this.sysQwSopSettingType = response.data;
+    });
+
+
+    this.form.sendType= this.$route.params && this.$route.params.command;
+
+    setTimeout(() => {
+      this.getList();
+    }, 200);
+
+  },
+  methods: {
+
+    addContent(index){
+
+      if (this.setting[index].content.length>0 && this.form.sendType==1){
+        return this.$message.error("因为企微接口限制,企微模板一天只能设置一条消息~")
+      }else {
+        this.setting[index].content.push({type:1,contentType:'1'})
+        this.videoList[index].push([])
+      }
+
+    },
+    delContent(index,contentIndex){
+      this.setting[index].content.splice(contentIndex,1)
+      this.videoList[index].splice(contentIndex,1)
+    },
+    addSetting(){
+
+      this.setting.push({name:null,content:[{type:1,contentType:'1'}]})
+      this.videoList.push([])
+
+    },
+    delSetting(index){
+      this.setting.splice(index,1)
+       this.videoList.splice(index,1)
+    },
+    //上传文件
+    handleAvatarSuccessFile(res, file, content) {
+      if (res.code === 200) {
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'fileUrl', res.url);
+      } else {
+        this.msgError(res.msg);
+      }
+    },
+    beforeAvatarUploadFile(file){
+      const isLt1M = file.size / 1024 / 1024 < 30;
+      if (!isLt1M) {
+        this.$message.error('上传大小不能超过 30MB!');
+      }
+      return isLt1M;
+    },
+
+    //下载文件
+    downloadUrl(materialUrl) {
+      // 直接返回文件 URL
+      return materialUrl;
+    },
+
+    handleAvatarSuccessVideo(res, file, content) {
+      if(res.code==200){
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'videoUrl', res.url);
+      }
+      else{
+        this.msgError(res.msg);
+      }
+    },
+    handleAvatarSuccessVoice(res, file, content){
+      if(res.code==200){
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'voiceUrl', res.url);
+      }
+      else{
+        this.msgError(res.msg);
+      }
+    },
+    beforeAvatarUploadVideo(file){
+      const isLt30M = file.size / 1024 / 1024 < 50;
+      const isMP4 = file.type === 'video/mp4';
+
+      if (!isMP4) {
+        this.$message.error('仅支持上传 MP4 格式的视频文件!');
+        return false;
+      }
+
+      if (!isLt30M) {
+        this.$message.error('上传大小不能超过 50MB!');
+        return false;
+      }
+
+      return true;
+    },
+
+    beforeAvatarUploadVoice(file){
+      const isLt10M = file.size / 1024 / 1024 < 10; // 假设语音文件大小限制为10MB
+      const isVoiceType = ['audio/mp3'].includes(file.type); // 支持的语音文件类型
+
+      if (!isVoiceType) {
+        this.$message.error('仅支持上传 MP3 格式的语音文件!');
+        return false;
+      }
+
+      if (!isLt10M) {
+        this.$message.error('上传大小不能超过 10MB!');
+        return false;
+      }
+
+      return true;
+    },
+
+    handleClosegroupUser(list){
+      const index = this.userSelectList.findIndex(t => t === list);
+      if (index !== -1) {
+        this.userSelectList.splice(index, 1);
+      }
+    },
+    courseChange(content,index,countIndex){
+
+      this.$set(content, 'videoId', null);
+
+      //如果是链接的才上
+      if (content.contentType==3 && content.type==2 && content.courseId!=null){
+
+        // 查找选中的课程对应的 label
+        const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
+
+        //响应式直接给链接的标题/封面上值
+        if (selectedCourse) {
+          this.$set(content, 'linkTitle', selectedCourse.dictLabel);
+          this.$set(content, 'linkImageUrl', selectedCourse.dictImgUrl);
+        }
+      }
+
+      videoList(content.courseId).then(response => {
+
+        this.videoList[index].splice(countIndex, 1, response.list);
+
+      });
+    },
+
+    handleContentTypeChange(content,index,countIndex){
+
+      //如果是链接的才上
+      if (content.contentType==3 && content.type==2 ) {
+
+        if (content.courseId!=null) {
+            // 查找选中的课程对应的 label
+            const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
+
+            //响应式直接给链接的标题/封面上值
+            if (selectedCourse) {
+              this.$set(content, 'linkTitle', selectedCourse.dictLabel);
+              this.$set(content, 'linkImageUrl', selectedCourse.dictImgUrl);
+            }
+        }
+        if (content.videoId!=null){
+            // 查找选中的课程对应的 label
+            const selectedVideo = this.videoList[index][countIndex].find(course => parseInt(course.dictValue) === content.videoId);
+
+            //响应式直接给链接的描述上值
+            if (selectedVideo) {
+              this.$set(content, 'linkDescribe', selectedVideo.dictLabel);
+            }
+        }
+
+      }
+    },
+    videoIdChange(content,index,countIndex){
+
+      //选择了课程小节则 默认绑上
+      this.$set(content,'isBindUrl','1');
+
+      //如果是链接的才上
+      if (content.contentType==3 && content.type==2 && content.videoId!=null ) {
+
+        // 查找选中的课程对应的 label
+        const selectedVideo = this.videoList[index][countIndex].find(course => parseInt(course.dictValue) === content.videoId);
+
+        //响应式直接给链接的描述上值
+        if (selectedVideo) {
+          this.$set(content, 'linkDescribe', selectedVideo.dictLabel);
+        }
+      }
+    },
+    /** 查询sop模板列表 */
+    getList() {
+      this.loading = true;
+      listSopTemp(this.queryParams).then(response => {
+        this.sopTempList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.$store.dispatch("tagsView/delView", this.$route);
+      this.$router.replace('/qw/conversion/sopTemp')
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        setting: null,
+        status: "0",
+        sort: null,
+        createTime: null,
+        createBy: null,
+        companyId: 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 = "添加sop模板";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getSopTemp(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改sop模板";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+
+        if (valid) {
+          this.form.setting=JSON.stringify(this.setting)
+
+          if(this.setting.length<=0){
+            return this.$message("请添加规则")
+          }
+          for (let i = 0; i < this.setting.length; i++) {
+
+            for (let j = 0; j < this.setting[i].content.length; j++) {
+
+              if(this.setting[i].name==null||this.setting[i].name==""){
+                return this.$message.error("内容名称不能为空")
+              }
+              if(this.setting[i].content[j].time==null||this.setting[i].content[j].time==""){
+                return this.$message.error("时间不能为空")
+
+              }
+              if (this.setting[i].content[j].contentType==1 && (this.setting[i].content[j].value==null|| this.setting[i].content[j].value=="")){
+                return this.$message.error("内容不能为空")
+              }
+              if (this.setting[i].content[j].contentType==2 && (this.setting[i].content[j].imgUrl==null|| this.setting[i].content[j].imgUrl=="")){
+                return this.$message.error("图片不能为空")
+              }
+              if (this.setting[i].content[j].contentType==3 && (this.setting[i].content[j].linkTitle==null|| this.setting[i].content[j].linkTitle=="")){
+                return this.$message.error("链接标题不能为空")
+              }
+              if (this.setting[i].content[j].contentType==3 && (this.setting[i].content[j].linkDescribe==null|| this.setting[i].content[j].linkDescribe=="")){
+                return this.$message.error("链接描述不能为空")
+              }
+              if (this.setting[i].content[j].contentType==3 && (this.setting[i].content[j].linkImageUrl==null|| this.setting[i].content[j].linkImageUrl=="")){
+                return this.$message.error("链接图片不能为空")
+              }
+              if (this.setting[i].content[j].contentType==3 && this.setting[i].content[j].type==1 && (this.setting[i].content[j].linkUrl==null|| this.setting[i].content[j].linkUrl=="")){
+                return this.$message.error("链接地址不能为空")
+              }
+              if (this.setting[i].content[j].contentType==5 && (this.setting[i].content[j].fileUrl==null|| this.setting[i].content[j].fileUrl=="")){
+                return this.$message.error("文件不能为空")
+              }
+              if (this.setting[i].content[j].contentType==6 && (this.setting[i].content[j].videoUrl==null|| this.setting[i].content[j].videoUrl=="")){
+                return this.$message.error("视频不能为空")
+              }
+              if (this.setting[i].content[j].contentType==7 && (this.setting[i].content[j].voiceUrl==null|| this.setting[i].content[j].voiceUrl=="")){
+                return this.$message.error("语音不能为空")
+              }
+            }
+
+          }
+
+          if (this.form.id != null) {
+            updateSopTemp(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.$store.dispatch("tagsView/delView", this.$route);
+              // this.$router.replace('/qw/conversion/sopTemp')
+              window.location.replace('/qw/conversion/sopTemp')
+              this.reset();
+
+            });
+          }
+          else {
+
+            addSopTemp(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.$store.dispatch("tagsView/delView", this.$route);
+              // this.$router.replace('/qw/conversion/sopTemp')
+              window.location.replace('/qw/conversion/sopTemp')
+              this.reset();
+
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除sop模板编号为"' + ids + '"的数据项?', "警告", {
+          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(() => {});
+    }
+  }
+};
+</script>
+
+<style scoped>
+.custom-input /deep/ .el-input__inner {
+  height: 20px;
+  text-align:center;
+}
+.custom-input /deep/ .el-input__icon {
+  line-height: 10px;
+}
+</style>

+ 194 - 0
src/views/qw/sopTemp/addTemp.vue

@@ -0,0 +1,194 @@
+<template>
+  <div class="app-container">
+
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 "> sop规则【企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 "> sop规则【AI插件】模板</div>
+
+    <div style="margin-top: 10px;margin-left: 50px;margin-right: 100px;margin-bottom: 60px;">
+      <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="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>
+      <div slot="footer" class="dialog-footer" style="float: right;">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {addTemp} from "@/api/qw/sopTemp";
+import ImageUpload from "@/views/qw/sop/ImageUpload";
+import FriendMaterial from "@/views/qw/friendMaterial/index.vue";
+import userVideo from "@/views/qw/userVideo/userVideo.vue";
+
+export default {
+  name: "addSopTemp",
+  components: { ImageUpload,FriendMaterial,userVideo},
+  data() {
+    return {
+
+      //图片放大
+      dialogVisible: false,
+      dialogImageUrl:null,
+
+      uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS2",
+      uploadUrlByVoice:process.env.VUE_APP_BASE_API+"/common/uploadOSSByHOOKVoice",
+      //上传语音的遮罩层
+      voiceLoading :false,
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      sysFsSopWatchStatus: [],
+      //消息内容类型 企微版
+      sysQwSopContentType:[],
+      //插件版
+      sysQwSopAiContentType:[],
+      //链接版类别
+      sysQwSopSettingType:[],
+
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // sop模板表格数据
+      setting: [],
+      courseList:[],
+      videoList:[],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 状态字典
+      statusOptions: [],
+      // 查询参数
+      form: {
+        name: null,
+        setting: null,
+        status: "1",
+        sort: 1,
+        companyId: null,
+        gap:1,
+      },
+      videoNumOptions:{
+        title:'选择视频号',
+        open:false,
+        content:null,
+        contentIndex:null,
+        setIndex:null,
+      },
+      // 表单校验
+      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' }
+        ],
+      },
+      contentRules:{
+        time:[{ required: true, message: '时间不能为空', trigger: 'blur' }],
+      }
+    };
+  },
+  created() {
+
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+    this.form.sendType = this.$route.params && this.$route.params.command;
+    setTimeout(() => {
+      this.getList();
+    }, 200);
+
+  },
+  methods: {
+    // 取消按钮
+    cancel() {
+      this.$store.dispatch("tagsView/delView", this.$route);
+      this.$router.replace('/qw/conversion/sopTemp')
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        setting: null,
+        status: "0",
+        sort: null,
+        createTime: null,
+        createBy: null,
+        companyId: null,
+      };
+      this.resetForm("form");
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateTemp(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.$store.dispatch("tagsView/delView", this.$route);
+              // this.$router.replace('/qw/conversion/sopTemp')
+              window.location.replace('/qw/conversion/sopTemp')
+              this.reset();
+            });
+          } else {
+            addTemp(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.$store.dispatch("tagsView/delView", this.$route);
+              // this.$router.replace('/qw/conversion/sopTemp')
+              window.location.replace('/qw/conversion/sopTemp')
+              this.reset();
+            });
+          }
+        }
+      });
+    },
+  }
+};
+</script>
+
+<style scoped>
+.custom-input /deep/ .el-input__inner {
+  height: 20px;
+  text-align: center;
+}
+
+.custom-input /deep/ .el-input__icon {
+  line-height: 10px;
+}
+</style>

+ 680 - 0
src/views/qw/sopTemp/index.vue

@@ -0,0 +1,680 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="销售公司" prop="companyId">
+        <el-select filterable v-model="queryParams.companyId" clearable placeholder="请选择公司名" size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
+      </el-form-item>
+      <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-button-->
+        <!--          type="primary"-->
+        <!--          plain-->
+        <!--          icon="el-icon-plus"-->
+        <!--          size="mini"-->
+        <!--          @click="handleAdd"-->
+        <!--          v-hasPermi="['qw:sopTemp:add']"-->
+        <!--        >新增</el-button>-->
+
+        <el-dropdown
+          @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: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="['qw:sopTemp:export']"
+        >导出
+        </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:edit']"
+          >复制模板
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-s-promotion"
+            @click="handleQueryDetails(scope.row)"
+            v-hasPermi="['qw:sopTemp:list']"
+          >详情
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['qw:sopTemp:edit']"
+          >修改
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate2(scope.row)"
+            v-hasPermi="['qw:sopTemp:edit']"
+          >管理规则
+          </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:edit']"
+          >红包设置
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['qw:sopTemp:remove']"
+          >删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入模板标题"/>
+        </el-form-item>
+        <el-form-item label="销售公司" prop="companyId">
+          <el-select filterable v-model="form.companyId" placeholder="请选择公司名" size="small">
+            <el-option
+              v-for="item in companys"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
+        </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="courseId" v-if="form.sendType == 5 && !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="每天发送次数" prop="num" v-if="form.sendType == 5 && !form.id">
+          <el-input-number v-model="form.num" :min="1" label="每天发送次数" @change="sendNumChange"></el-input-number>
+        </el-form-item>
+        <el-form-item label="发送时间" v-if="form.sendType == 5 && !form.id">
+          <el-time-picker
+            v-for="item in form.timeList"
+            class="custom-input"
+            v-model="item.value"
+            value-format="HH:mm"
+            format="HH:mm"
+            :picker-options="{ selectableRange: startTimeRange }"
+            placeholder="时间"
+            style="width: 100px;height: 20px;margin-left: 10px;margin-top: 10px">
+          </el-time-picker>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer" style="float: right;">
+        <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="float: right;">
+        <el-button type="primary" @click="updateRedData" :disabled="redData.loading">保 存</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  addTemp,
+  copyTemplate,
+  delSopTemp,
+  exportSopTemp,
+  getSelectableRange,
+  getSopTemp,
+  redList,
+  listSopTemp,
+  shareSopTemp,
+  updateRedPackage,
+  updateTemp
+} from "@/api/qw/sopTemp";
+import {getCompanyList} from "@/api/company/company";
+import {courseList} from "@/api/qw/sop";
+import fa from "element-ui/src/locale/lang/fa";
+
+export default {
+  name: "SopTemp",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      companysloading: false,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      courseList: [],
+      //选中的公司
+      companys: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      companyTotal: 0,
+      total: 0,
+      sendType: 0,
+      // sop模板表格数据
+      sopTempList: [],
+      sysQwSopType: [],
+      companyList: [],
+      projectOptions: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      command: 0,
+      // 状态字典
+      statusOptions: [],
+      startTimeRange: [],
+
+      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'}
+        ],
+      },
+      contentRules: {
+        time: [{required: true, message: '时间不能为空', trigger: 'blur'}],
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = 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;
+    });
+
+    getCompanyList().then(response => {
+      this.companys = response.data;
+    });
+  },
+  methods: {
+
+    handleCompanyQuery() {
+      this.queryCompanyParams.pageNum = 1;
+    },
+    resetCompanyQuery() {
+      this.resetForm("queryCompanyForm");
+      this.handleCompanyQuery();
+    },
+    /** 查询sop模板列表 */
+    getList() {
+      this.loading = true;
+      listSopTemp(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,
+        num: 1,
+        timeList: [{value: ""}],
+        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/sopTempe/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/sopTempe/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) {
+          let f = JSON.parse(JSON.stringify(this.form));
+          if (f.timeList && f.timeList.length > 0) {
+            f.timeList = f.timeList.map(item => item.value);
+          }
+          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('是否确认删除sop模板编号为"' + ids + '"的数据项?', "警告", {
+        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: ""});
+        }
+      } else {
+        let len = old - val;
+        this.form.timeList.splice(Math.max(this.form.timeList.length - len, 0), len);
+      }
+    }
+  }
+};
+</script>

+ 177 - 0
src/views/qw/sopTemp/sopTemp.vue

@@ -0,0 +1,177 @@
+<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>
+        <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-table v-loading="loading" :data="sopTempList" ref="sopTempList" >
+      <el-table-column label="id" align="center" prop="id" />
+      <el-table-column label="模板标题" align="center" prop="name" />
+      <el-table-column label="每天首条消息是否发课" align="center" prop="name" v-if="cuoser">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.cuoser" type="success">是</el-tag>
+          <el-tag v-if="!scope.row.cuoser" type="danger">否</el-tag>
+        </template>
+      </el-table-column>
+
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="medium"
+            type="primary"
+            plain
+            icon="el-icon-edit"
+            @click="handleSopTemp(scope.row)"
+            v-hasPermi="['qw:sopTemp:edit']"
+          >选择此模板</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getResetList"
+    />
+  </div>
+</template>
+
+<script>
+import { listSopTemp } from "@/api/qw/sopTemp";
+
+export default {
+  name: "SopTempComments",
+  data() {
+    return {
+      isUpdateSop:null,
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // sop模板表格数据
+      sopTempList: [],
+      sysQwSopType:[],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      cuoser: false,
+      // 状态字典
+      statusOptions: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        sendType:null,
+        status:1,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+
+    this.getDicts("sys_qw_sop_type").then(response => {
+      this.sysQwSopType = response.data;
+    });
+  },
+  methods: {
+    /** 查询sop模板列表 */
+    getList(sendType,isUpdate,cuoser) {
+      if(cuoser == null){
+        cuoser = false;
+      }
+      console.log(isUpdate)
+      this.isUpdateSop = isUpdate;
+      this.cuoser = cuoser;
+      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;
+      });
+    },
+
+    getResetList(){
+      this.loading = true;
+      listSopTemp(this.queryParams).then(response => {
+        this.sopTempList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    handleSopTemp(val){
+      if (this.isUpdateSop === 1){
+        console.log("进入sop修改模板方法")
+        this.$emit("submitUpdateTemp",val)
+        this.$refs.sopTempList.clearSelection();
+      }else {
+        this.$emit("sopTemp",val)
+        this.$refs.sopTempList.clearSelection();
+        this.open=false;
+      }
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        status:1,
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getResetList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+
+
+  }
+};
+</script>

+ 327 - 0
src/views/qw/sopTemp/updateAiChatTemp.vue

@@ -0,0 +1,327 @@
+<template>
+  <div class="app-container">
+    <div style="margin: 30px;">sop规则【AI对话】模板</div>
+    <div style="margin-top: 10px;margin-left: 50px;margin-right: 100px;margin-bottom: 60px;">
+      <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="sort">
+          <el-input-number v-model="form.sort" :min="0" label="排序"></el-input-number>
+        </el-form-item>
+        <el-form-item label="规则" prop="setting">
+          <el-timeline>
+            <el-timeline-item
+              :timestamp="'第'+(1+(form.gap*index))+'天'"
+              :color="'#0bbd87'"
+              placement="top"
+              v-for="(item, index) in setting"
+              :key="index"
+              style="margin-top: 10px;">
+              <el-row>
+                <el-col :span="22">
+                  <div style="background-color: #fbfbfb;padding: 15px; border: 1px solid #e6e6e6; margin-bottom: 20px;">
+                    <el-form :model="item" label-width="80px">
+                      <el-form-item label="规则">
+                        <div v-for="(content, contentIndex) in item.content"
+                             :key="contentIndex"
+                             style="background-color: #fdfdfd;padding: 15px; border: 1px solid #e6e6e6; margin-bottom: 20px;">
+                          <el-row>
+                            <el-col :span="22">
+                              <el-form :model="content" label-width="70px">
+                                <!-- 第一天不显示时间选择 -->
+                                <el-form-item label="时间" prop="time" v-if="index > 0">
+                                  <el-time-picker
+                                    class="custom-input"
+                                    v-model="content.time"
+                                    value-format="HH:mm"
+                                    format="HH:mm"
+                                    :picker-options="{ selectableRange: '00:00:00 - 23:59:59' }"
+                                    placeholder="时间"
+                                    style="width: 100px;height: 20px;">
+                                  </el-time-picker>
+                                </el-form-item>
+
+
+                                <div v-for="(setList, setIndex) in content.setting"
+                                     :key="setIndex"
+                                     style="background-color: #fdfdfd; border: 1px solid #e6e6e6; margin-bottom: 20px;">
+                                  <el-row>
+                                    <el-col :span="22">
+                                      <el-form :model="setList" label-width="70px">
+										  <el-form-item label="间隔时间" prop="intervalTime" style="margin: 2%">
+										    <el-input-number
+										      v-model="setList.intervalTime"
+										      :min="1"
+										      :max="1440"
+										      style="width:100;margin-top: 10px;"
+										      >
+										    </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>
+                        </el-col>
+                            <el-col :span="1" :offset="1">
+                              <i class="el-icon-delete"
+                                 @click="delSetList(index,contentIndex,setIndex)"
+                                 style="margin-top: 20px;"
+                                 v-if="content.setting.length>1 && (formType==1 || formType==2)"></i>
+                            </el-col>
+                      </el-row>
+                    </div>
+                                <el-link type="primary"
+                                        class="el-icon-plus"
+                                        :underline="false"
+                                        @click='addSetList(contentIndex,item.content)' v-if="(formType==1 || formType==2)">添加内容</el-link>
+                              </el-form>
+                            </el-col>
+                            <el-col :span="1" :offset="1">
+                              <i class="el-icon-delete"
+                                 @click="delContent(index,contentIndex)"
+                                 style="margin-top: 20px;"
+                                 v-if="item.content.length>1 && (formType==1 || formType==2)"></i>
+                            </el-col>
+                          </el-row>
+                        </div>
+                        <el-link type="primary"
+								                v-if="index > 0 && (formType==1 || formType==2)"
+                                class="el-icon-plus"
+                                :underline="false"
+                                @click='addContent(index)' >添加规则</el-link>
+                      </el-form-item>
+                    </el-form>
+                  </div>
+                </el-col>
+                <el-col :span="1" :offset="1">
+                  <i class="el-icon-delete"
+                     @click="delSetting(index)"
+                     v-if="setting.length>1 && (formType==1 || formType==2) "></i>
+                </el-col>
+              </el-row>
+            </el-timeline-item>
+          </el-timeline>
+<!--          <el-link type="primary"
+                   class="el-icon-plus"
+                   :underline="false"
+                   @click='addSetting()'>添加天数</el-link> -->
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer" style="float: right;"  v-if="this.formType==1 || this.formType==2 ">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { addSopTemp ,getSopTemp,updateSopTemp} from "@/api/qw/sopTemp";
+
+export default {
+  name: "AddAiChatTemp",
+  data() {
+    return {
+      // 状态字典
+      statusOptions: [],
+      // 添加推送方式字典
+      sysQwSopType: [],
+      // 表单数据
+      form: {
+        name: null,
+        setting: null,
+        status: "1",
+        sort: 1,
+        gap: 1,
+        sendType: 3, // AI对话类型,固定值
+      },
+      //1 修改 2 复制 3 查看
+      formType:null,
+
+      // 规则设置
+      setting: [],
+      // 表单校验
+      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' }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+    // 获取推送方式字典
+    this.getDicts("sys_qw_sop_type").then(response => {
+      this.sysQwSopType = response.data;
+    });
+	const id = this.$route.params && this.$route.params.id;
+	this.formType = this.$route.params && this.$route.params.type;
+	this.handleUpdate(id);
+
+  },
+  methods: {
+
+	handleUpdate(id) {
+	  getSopTemp(id).then(response => {
+		this.form = response.data;
+	    this.setting = JSON.parse(response.data.setting);
+	  });
+	},
+    addSetList(contentIndex, content) {
+      content[contentIndex].setting.push({
+		intervalTime: 5,
+        contentType: '1',
+        value: ""
+      });
+    },
+
+    delSetList(index, contentIndex, setIndex) {
+      this.setting[index].content[contentIndex].setting.splice(setIndex, 1);
+    },
+
+    addContent(index) {
+      this.setting[index].content.push({
+        type: 1,
+        contentType: '1',
+         // 默认5分钟
+        setting: [{
+          intervalTime: 5,
+          contentType: '1',
+          value: ""
+        }]
+      });
+    },
+
+    delContent(index, contentIndex) {
+      this.setting[index].content.splice(contentIndex, 1);
+    },
+
+    addSetting() {
+      this.setting.push({
+        content: [{
+          type: 1,
+          contentType: '1',
+
+          setting: [{
+			intervalTime: 5, // 默认5分钟
+            contentType: '1',
+            value: ""
+          }]
+        }]
+      });
+    },
+
+    delSetting(index) {
+      this.setting.splice(index, 1);
+    },
+
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          // 验证内容
+          for(let day of this.setting) {
+            for(let content of day.content) {
+              if(content.time === undefined && this.setting.indexOf(day) > 0) {
+                return this.$message.error("时间不能为空");
+              }
+              for(let set of content.setting) {
+				if(!set.intervalTime) {
+				  return this.$message.error("间隔时间不能为空");
+				}
+                if(!set.value) {
+                  return this.$message.error("内容不能为空");
+                }
+              }
+            }
+          }
+          this.form.setting = JSON.stringify(this.setting);
+
+		  if (this.formType == 1) {
+		    if (this.form.id != null) {
+		      updateSopTemp(this.form).then(response => {
+		        this.msgSuccess("修改成功");
+		        this.$store.dispatch("tagsView/delView", this.$route);
+		        window.location.replace('/qw/conversion/sopTemp')
+		        this.reset();
+		      });
+		    }
+		  } else {
+		    //id制空,防止键值重复-报错
+		    this.form.id = null
+		    //更新时间制空
+		    this.form.updateTime = null
+		    addSopTemp(this.form).then(response => {
+		     this.msgSuccess("复制成功");
+		     this.$store.dispatch("tagsView/delView", this.$route);
+		     window.location.replace('/qw/conversion/sopTemp')
+		     this.reset();
+		    });
+		  }
+
+
+
+        }
+      });
+    },
+
+    cancel() {
+      this.$router.push('/qw/conversion/sopTemp');
+    }
+  }
+};
+</script>
+
+<style scoped>
+.custom-input /deep/ .el-input__inner {
+  height: 20px;
+  text-align: center;
+}
+
+.custom-input /deep/ .el-input__icon {
+  line-height: 10px;
+}
+
+.tip {
+  color: #909399;
+  font-size: 12px;
+  margin-left: 10px;
+}
+</style>

+ 1741 - 0
src/views/qw/sopTemp/updateSopTemp.vue

@@ -0,0 +1,1741 @@
+<template>
+  <div class="app-container">
+
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 && formType==1 "> sop规则【修改企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 && formType==2 "> sop规则【复制企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 && formType==3 "> sop规则【查看企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==1 "> sop规则【修改群发助手】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==2 "> sop规则【复制群发助手】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==3 "> sop规则【查看群发助手】模板</div>
+    <div style="margin: 30px;">模板编号:【{{ this.form.id }}】</div>
+
+    <div style="margin-top: 10px;margin-left: 50px;margin-right: 100px;margin-bottom: 60px;">
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="名称" prop="name">
+          {{ form.name }}
+        </el-form-item>
+
+        <el-form-item label="状态">
+          <el-tag v-for="dict in statusOptions" v-if="dict.dictValue == form.status">{{ dict.dictLabel }}</el-tag>
+        </el-form-item>
+        <el-form-item label="间隔天数" prop="gap">
+          {{ form.gap }}天
+        </el-form-item>
+        <el-form-item label="规则" prop="setting">
+          <el-link type="primary" class="el-icon-plus" :underline="false" @click='addSetting()'
+                   v-if="form.sendType != 4 && formType != 3">添加天数
+          </el-link>
+          <el-link type="primary" class="el-icon-sort" :underline="false" @click='openUpdateDaySorts()'
+                   style="margin-left: 20px"
+                   v-if="form.sendType != 4 && formType != 3">修改天数排序
+          </el-link>
+          <el-link type="primary" class="el-icon-sort" :underline="false" @click='openUpdateSorts()'
+                   style="margin-left: 20px"
+                   v-if="form.sendType != 4 && formType != 3">修改规则排序
+          </el-link>
+          <el-link type="primary" class="el-icon-plus" :underline="false" @click='addSetting()'
+                   v-if="form.sendType == 4 && (formType != 3 && setting.length < 1)">添加天数
+          </el-link>
+          <el-tabs v-model="tabIndex" type="card" @tab-remove="delSetting" v-if="setting && setting.length > 0"
+                   :before-leave="leave" @tab-click="tabClick">
+            <el-tab-pane v-for="(item, index) in setting" :closable="formType != 3" :key="index" :name="index + ''">
+              <el-badge slot="label" :is-dot="!item.id" class="item" style="display: inline-block">
+                <span>{{ '第' + item.dayNum + '天' }}</span>
+              </el-badge>
+              <el-row v-loading="loading">
+                <el-col :span="22">
+                  <div
+                    style="background-color: #fbfbfb;padding: 15px;  border: 1px solid #e6e6e6; margin-bottom: 20px;">
+                    <el-form :model="item" label-width="80px">
+                      <el-form-item v-if="form.sendType != 4" label="内容名称" style="height: 50px;">
+                        <el-input :disabled="formType == 3" v-model="item.name"
+                                  placeholder="内容名称,仅内部可见"/>
+                      </el-form-item>
+                      <el-form-item label="课程" v-if="form.sendType == 5 && item.content && item.content.length > 0" required>
+                        <el-select :disabled="(formType == 3 || form.sendType == 5) && item.id != null" v-model="item.content[0].courseId"
+                                   placeholder="请选择课程" style=" margin-right: 10px;" size="mini" remote
+                                   filterable
+                                   @change="courseChangeUpdate(item.content[0], index, 0)">
+                          <el-option
+                            v-for="dict in courseList"
+                            :key="dict.dictValue"
+                            :label="dict.dictLabel"
+                            :value="parseInt(dict.dictValue)"
+                          />
+                        </el-select>
+                        <el-select :disabled="(formType == 3 || form.sendType == 5) && item.id != null" v-model="item.content[0].videoId"
+                                   placeholder="请选择小节" size="mini" style=" margin-right: 10px;" remote
+                                   filterable
+                                   @change="videoIdChange(item.content[0],index,0)">
+                          <el-option
+                            v-for="dict in videoList[0]"
+                            :key="dict.dictValue"
+                            :label="dict.dictLabel"
+                            :value="parseInt(dict.dictValue)"
+                          />
+                        </el-select>
+                      </el-form-item>
+                      <el-form-item label="规则">
+                        <div v-for="(content, contentIndex) in item.content" :key="contentIndex"
+                             style="background-color: #fdfdfd;padding: 15px;  border: 1px solid #e6e6e6; margin-bottom: 20px;">
+                          <el-row>
+                            <el-col el-col :span="22">
+                              <el-form :model="content" label-width="70px">
+                                <div v-if="item.dayNum==1">
+                                  <el-form-item label="时间" v-if="form.sendType != 4">
+                                    <el-time-picker
+                                      :disabled="formType == 3"
+                                      class="custom-input"
+                                      v-model="content.time"
+                                      value-format="HH:mm"
+                                      format="HH:mm"
+                                      :picker-options="{ selectableRange: startTimeRange }"
+                                      placeholder="时间"
+                                      style="width: 100px;height: 20px;">
+                                    </el-time-picker>
+                                  </el-form-item>
+                                </div>
+                                <div v-else>
+                                  <el-form-item label="时间" v-if="form.sendType != 4">
+                                    <el-time-picker
+                                      :disabled="formType == 3"
+                                      class="custom-input"
+                                      v-model="content.time"
+                                      value-format="HH:mm"
+                                      format="HH:mm"
+                                      placeholder="时间"
+                                      style="width: 100px;height: 20px;">
+                                    </el-time-picker>
+                                  </el-form-item>
+                                </div>
+                                <el-form-item label="官方群发" v-if="contentIndex==0 && content.type==2">
+                                  <el-switch
+                                    v-model="content.isOfficial"
+                                    active-color="#13ce66"
+                                    inactive-color="#DCDFE6"
+                                    active-value="1"
+                                    inactive-value="0">
+                                  </el-switch>
+                                </el-form-item>
+                                <el-form-item label="消息类型" v-if="form.sendType != 4">
+                                  <el-select :disabled="formType == 3" v-model="content.courseType"
+                                             placeholder="请选择消息类型" size="mini"
+                                             style=" margin-right: 10px;" v-if="content.type != 4 ">
+                                    <el-option
+                                      v-for="dict in sysFsSopWatchStatus"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="Number(dict.dictValue)"
+                                    />
+                                  </el-select>
+                                </el-form-item>
+
+                                <el-form-item label="消息类别" v-if="form.sendType != 4 && form.sendType != 5">
+                                  <el-radio-group v-model="content.type"
+                                                  :disabled="formType == 3 || content.isOfficial === '1'"
+                                                  @change="updateHtml(() => content.contentType = '1')">
+                                    <el-radio :label="1">普通</el-radio>
+                                    <el-radio :label="2">课程</el-radio>
+                                    <el-radio :label="4">AI触达</el-radio>
+                                    <el-radio :label="5">打标签</el-radio>
+                                  </el-radio-group>
+                                </el-form-item>
+                                <el-form-item label="课程" v-if="content.type == 2 && form.sendType != 5" required>
+                                  <el-select :disabled="formType == 3 || form.sendType == 5" v-model="content.courseId"
+                                             placeholder="请选择课程" style=" margin-right: 10px;" size="mini" remote
+                                             filterable
+                                             @change="courseChangeUpdate(content,index,contentIndex)">
+                                    <el-option
+                                      v-for="dict in courseList"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="parseInt(dict.dictValue)"
+                                    />
+                                  </el-select>
+                                  <el-select :disabled="formType == 3 || form.sendType == 5" v-model="content.videoId"
+                                             placeholder="请选择小节" size="mini" style=" margin-right: 10px;" remote
+                                             filterable
+                                             @change="videoIdChange(content,index,contentIndex)">
+                                    <el-option
+                                      v-for="dict in videoList[contentIndex]"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="parseInt(dict.dictValue)"
+                                    />
+                                  </el-select>
+                                  <el-select :disabled="formType == 3" v-model="content.courseType"
+                                             placeholder="请选择消息类型" size="mini"
+                                             style=" margin-right: 10px;" v-if="content.type != 4 ">
+                                    <el-option
+                                      v-for="dict in sysFsSopWatchStatus"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="Number(dict.dictValue)"
+                                    />
+                                  </el-select>
+
+                                </el-form-item>
+                                <el-form-item label="Ai触达" v-if="content.type == 4 ">
+                                  <el-select :disabled="formType == 3" v-model="content.aiTouch"
+                                             placeholder="请选择Ai触达类型" size="mini"
+                                             style=" margin-right: 10px;" v-if="content.type == 4 ">
+                                    <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="添加标签" prop="addTag" v-if="content.type == 5 "
+                                              v-model="content.addTag">
+
+                                  <el-tag
+                                    :key="tag"
+                                    v-for="tag in content.addTag"
+                                    closable
+                                    :disable-transitions="false"
+                                    @close="handleClose(contentIndex,tag,content)">
+                                    {{ tag }}
+                                  </el-tag>
+                                  <el-input
+                                    style="width:110px"
+                                    class="input-new-tag"
+                                    v-if="addTag[contentIndex].inputVisible"
+                                    v-model="addTag[contentIndex].inputValue"
+                                    ref="saveTagInput"
+                                    size="small"
+                                    @keyup.enter.native="handleInputConfirm(contentIndex,content)"
+                                    @blur="handleInputConfirm(contentIndex,content)"
+                                  >
+                                  </el-input>
+                                  <el-button v-else class="button-new-tag" size="small" style="width: 110px"
+                                             @click="showInput(contentIndex)">新增标签
+                                  </el-button>
+                                </el-form-item>
+
+                                <el-form-item label="移除标签" prop="remarkMobiles" v-if="content.type == 5 ">
+
+                                  <el-tag
+                                    :key="tag"
+                                    v-for="tag in content.delTag"
+                                    closable
+                                    :disable-transitions="false"
+                                    @close="handleCloseDel(contentIndex,tag,content)">
+                                    {{ tag }}
+                                  </el-tag>
+                                  <el-input
+                                    style="width:110px"
+                                    class="input-new-tag"
+                                    v-if="addTag[contentIndex].delTagVisible"
+                                    v-model="addTag[contentIndex].delTagValue"
+                                    ref="saveTagInputDel"
+                                    size="small"
+                                    @keyup.enter.native="handleInputConfirmDel(contentIndex,content)"
+                                    @blur="handleInputConfirmDel(contentIndex,content)"
+                                  >
+                                  </el-input>
+                                  <el-button v-else class="button-new-tag" size="small" style="width: 110px"
+                                             @click="showInputDel(contentIndex)">新增标签
+                                  </el-button>
+
+                                </el-form-item>
+
+                                <div v-for="(setList, setIndex) in content.setting" :key="setIndex"
+                                     style="background-color: #fdfdfd; border: 1px solid #e6e6e6; margin-bottom: 20px;"
+                                     v-if="content.type != 4 && content.type != 5">
+                                  <el-row style="padding-bottom: 20px">
+                                    <el-col :span="22">
+                                      <el-form v-if="form.sendType != 4" :model="setList" label-width="70px">
+                                        <el-form-item label="内容类别" style="margin: 2%">
+                                          <div v-if="form.sendType == 1 ">
+                                            <el-radio-group v-model="setList.contentType" :disabled="formType == 3">
+                                              <!--                                              <el-radio  :label="item.dictValue" v-for="item in sysQwSopContentType">{{item.dictLabel}}</el-radio>-->
+                                              <el-radio
+                                                v-for="item in sysQwSopContentType"
+                                                :key="item.dictValue"
+                                                :label="item.dictValue"
+                                                :disabled="item.dictValue === '1' && content.setting.some(s => s.contentType == '1') ">
+                                                {{ item.dictLabel }}
+                                              </el-radio>
+                                            </el-radio-group>
+                                          </div>
+                                          <div v-if="form.sendType == 2">
+                                            <el-radio-group v-model="setList.contentType"
+                                                            :disabled="formType == 3"
+                                                            @change="handleContentTypeChange(content,index,contentIndex,setIndex, item, 'contentType', $event)">
+                                              <el-radio
+                                                :key="item.dictValue"
+                                                :label="item.dictValue"
+                                                :disabled="(content.type!=2 && item.dictValue === '9') || (content.isOfficial==1 && ['5','6','7','8','9'].includes(item.dictValue))"
+                                                v-for="item in sysQwSopAiContentType">{{ item.dictLabel }}
+                                              </el-radio>
+                                            </el-radio-group>
+                                          </div>
+                                          <div v-if="form.sendType == 5">
+                                            <el-radio-group v-model="setList.contentType"
+                                                            :disabled="formType == 3 || (form.sendType == 5 && contentIndex != 0 && setIndex == 0)"
+                                                            @change="handleContentTypeChange(content,index,contentIndex,setIndex, item, 'contentType', $event)">
+                                              <el-radio
+                                                :key="item.dictValue"
+                                                :label="item.dictValue"
+                                                :disabled="(content.type!=2 && item.dictValue === '9') || (content.isOfficial==1 && ['5','6','7','8','9'].includes(item.dictValue))"
+                                                v-for="item in sysQwSopAiContentType" v-if="setIndex == 0 ? courseTypeList.includes(item.dictValue) : !courseTypeList.includes(item.dictValue)">{{ item.dictLabel }}
+                                              </el-radio>
+                                            </el-radio-group>
+                                          </div>
+                                        </el-form-item>
+                                        <el-form-item label="内容">
+                                          <el-input :disabled="formType == 3" v-if="setList.contentType == 1 "
+                                                    v-model="setList.value"
+                                                    type="textarea" :rows="3" placeholder="内容"
+                                                    style="width: 90%;margin-top: 10px;"
+                                                    @keydown.native="handleKeydown($event, index, contentIndex, setIndex)"
+                                                    :ref="`textarea-${index}-${contentIndex}-${setIndex}`"
+                                          />
+
+                                          <!-- 修改按钮部分 -->
+                                          <el-link
+                                            v-if="setList.contentType == 1"
+                                            type="primary"
+                                            @click="toggleSalesCall(index, contentIndex, setIndex)"
+                                            style="margin-top: 10px;"
+                                          >
+                                            {{ setList.isSalesCallAdded ? '移除#销售称呼#' : '添加#销售称呼#' }}
+                                          </el-link>
+
+                                          <ImageUpload :disabled="formType == 3" v-if="setList.contentType == 2 "
+                                                       v-model="setList.imgUrl"
+                                                       type="image" :num="1" :width="150" :height="150"/>
+
+                                          <div
+                                            v-if="setList.contentType == 3  || (setList.contentType == 9 && content.type==2 )">
+                                            <el-card class="box-card">
+                                              <el-form-item label="链接标题:" label-width="100px" required>
+                                                <el-input :disabled="formType == 3 || (form.sendType == 5 && contentIndex != 0 && setIndex == 0)" v-model="setList.linkTitle"
+                                                          @change="updateAll(setIndex, item, 'linkTitle', $event)"
+                                                          placeholder="请输入链接标题"
+                                                          style="width: 90%;"/>
+                                              </el-form-item>
+                                              <el-form-item label="链接描述:" label-width="100px" required>
+                                                <el-input :disabled="formType == 3 || (form.sendType == 5 && contentIndex != 0 && setIndex == 0)" type="textarea" :rows="3"
+                                                          v-model="setList.linkDescribe"
+                                                          @change="updateAll(setIndex, item, 'linkDescribe', $event)"
+                                                          placeholder="请输入链接描述"
+                                                          style="width: 90%;margin-top: 1%;"/>
+                                              </el-form-item>
+                                              <el-form-item label="链接封面:" label-width="100px" required>
+                                                <ImageUpload :disabled="formType == 3 || (form.sendType == 5 && contentIndex != 0 && setIndex == 0)" v-model="setList.linkImageUrl"
+                                                             type="image" :num="1"
+                                                             @input="updateAll(setIndex, item, 'linkImageUrl', $event)"
+                                                             :file-size="2" :width="150" :height="150"
+                                                             style="margin-top: 1%;"/>
+                                              </el-form-item>
+                                              <div v-if="content.type != 2 " style="margin-top: 1%">
+                                                <el-form-item label="链接地址:" label-width="100px" required>
+                                                  <el-input :disabled="formType == 3" v-model="setList.linkUrl"
+                                                            placeholder="请输入链接地址"
+                                                            style="width: 90%;"/>
+                                                </el-form-item>
+                                              </div>
+                                              <div v-if="content.type == 2 ">
+                                                <el-form-item label="链接地址:" label-width="100px">
+                                                  <el-tag type="warning" v-model="setList.isBindUrl = 1 ">选择的课程小节
+                                                    即为卡片链接地址
+                                                  </el-tag>
+                                                </el-form-item>
+                                              </div>
+                                            </el-card>
+                                          </div>
+
+                                          <div v-if="setList.contentType == 4">
+                                            <el-card class="box-card">
+                                              <el-form-item label="标题" prop="miniprogramTitle">
+                                                <el-input v-model="setList.miniprogramTitle"
+                                                          :disabled="formType == 3 || (form.sendType == 5 && contentIndex != 0 && setIndex == 0)"
+                                                          @change="updateAll(setIndex, item, 'miniprogramTitle', $event)"
+                                                          placeholder="请输入小程序消息标题,最长为64字节" :rows="2"
+                                                          maxlength="64" type="textarea"
+                                                          @input="checkByteLength(content,setList.contentType,content.isOfficial)"/>
+                                              </el-form-item>
+                                              <el-form-item label="封面" prop="miniprogramPicUrl">
+                                                <ImageUpload v-if="content.isOfficial !== '1'"
+                                                             @change="updateAll(setIndex, item, 'miniprogramPicUrl', $event)"
+                                                             :disabled="formType == 3 || (form.sendType == 5 && contentIndex != 0 && setIndex == 0)"
+                                                             v-model="setList.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="setList.miniprogramAppid='wx73f85f8d62769119' "
+                                                          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="setList.miniprogramPage"
+                                                          placeholder="小程序消息打开后的路径" disabled/>
+                                              </el-form-item>
+                                            </el-card>
+                                          </div>
+
+
+                                          <div v-if="setList.contentType == 5 ">
+
+                                            <el-form-item label="上传文件:" prop="fileUrl" label-width="100px">
+                                              <el-upload
+                                                :disabled="formType == 3"
+                                                v-model="setList.fileUrl"
+                                                class="avatar-uploader"
+                                                :action="uploadUrl"
+                                                :show-file-list="false"
+                                                :on-success="(res, file) => handleAvatarSuccessFile(res, file, setList)"
+                                                :before-upload="beforeAvatarUploadFile">
+                                                <i class="el-icon-plus avatar-uploader-icon"></i>
+                                              </el-upload>
+                                              <el-link v-if="setList.fileUrl" type="primary"
+                                                       :href="downloadUrl(setList.fileUrl)" download>
+                                                {{ setList.fileUrl }}
+                                              </el-link>
+                                            </el-form-item>
+
+                                          </div>
+
+                                          <div v-if="setList.contentType == 6 ">
+                                            <el-form-item label="上传视频:" prop="videoUrl" label-width="100px">
+                                              <el-upload
+                                                :disabled="formType == 3"
+                                                v-model="setList.videoUrl"
+                                                class="avatar-uploader"
+                                                :action="uploadUrl"
+                                                :show-file-list="false"
+                                                :on-success="(res, file) => handleAvatarSuccessVideo(res, file, setList)"
+                                                :before-upload="beforeAvatarUploadVideo">
+                                                <i class="el-icon-plus avatar-uploader-icon"></i>
+                                              </el-upload>
+                                              <video v-if="setList.videoUrl"
+                                                     :src="setList.videoUrl"
+                                                     controls style="width: 200px;height: 100px">
+                                              </video>
+                                            </el-form-item>
+                                          </div>
+                                          <div v-if="setList.contentType == 7 ">
+                                            <el-input
+                                              :disabled="formType == 3"
+                                              v-model="setList.value"
+                                              type="textarea" :rows="3" maxlength="66" show-word-limit
+                                              placeholder="输入要转为语音的内容"
+                                              style="width: 90%;margin-top: 10px;"
+                                              @input="handleInputVideoText(setList.value,setList)"/>
+
+
+                                          </div>
+                                          <div v-if="setList.contentType == 8 ">
+                                            <el-button :disabled="formType == 3" type="primary"
+                                                       style="margin-bottom: 1%"
+                                                       @click="hanldeSelectVideoNum(content,index,contentIndex,setIndex)">
+                                              选择视频号
+                                            </el-button>
+                                            <el-card class="box-card" v-if="setList.coverUrl">
+                                              <el-form-item label="封面标题:" label-width="100px">
+                                                <el-input :disabled="formType == 3" v-model="setList.nickname"
+                                                          style="width: 90%;margin-bottom: 1%" disabled/>
+                                              </el-form-item>
+                                              <el-form-item label="头像:" label-width="100px">
+                                                <el-image
+                                                  v-if="setList.avatar != null"
+                                                  :src="setList.avatar"
+                                                  :preview-src-list="[setList.avatar]"
+                                                  :style="{ width: '50px', height: '50px' }"
+                                                ></el-image>
+                                              </el-form-item>
+                                              <el-form-item label="封面:" label-width="100px">
+                                                <el-image
+                                                  v-if="setList.coverUrl != null"
+                                                  :src="setList.coverUrl"
+                                                  :preview-src-list="[setList.coverUrl]"
+                                                  :style="{ width: '200px', height: '200px' }"
+                                                ></el-image>
+
+                                              </el-form-item>
+                                              <el-form-item label="简介:" label-width="100px">
+                                                <el-input :disabled="formType == 3" type="textarea" :rows="3"
+                                                          v-model="setList.desc"
+                                                          style="width: 90%;margin-top: 1%;" disabled/>
+                                              </el-form-item>
+                                              <el-form-item label="视频地址:" label-width="100px"
+                                                            style="margin-top: 1%">
+                                                <el-input :disabled="formType == 3" v-model="setList.url"
+                                                          style="width: 90%;" disabled/>
+                                              </el-form-item>
+
+                                            </el-card>
+                                          </div>
+                                          <div v-if="setList.contentType == 10 ">
+                                            <el-card class="box-card">
+                                              <el-form-item label="链接标题:" label-width="100px" required>
+                                                <el-input :disabled="formType == 3" v-model="setList.linkTitle"
+                                                          placeholder="请输入链接标题"
+                                                          style="width: 90%;"/>
+                                              </el-form-item>
+                                              <el-form-item label="链接描述:" label-width="100px" required>
+                                                <el-input :disabled="formType == 3" type="textarea" :rows="3"
+                                                          v-model="setList.linkDescribe"
+                                                          placeholder="请输入链接描述"
+                                                          style="width: 90%;margin-top: 1%;"/>
+                                              </el-form-item>
+                                              <el-form-item label="链接封面:" label-width="100px" required>
+                                                <ImageUpload :disabled="formType == 3" v-model="setList.linkImageUrl"
+                                                             type="image" :num="1"
+                                                             :file-size="2" :width="150" :height="150"
+                                                             style="margin-top: 1%;"/>
+                                              </el-form-item>
+                                              <el-form-item label="链接地址:" label-width="100px">
+                                                <el-tag type="warning"> 链接地址自动生成
+                                                </el-tag>
+                                              </el-form-item>
+                                            </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"
+                                              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 != 2  && setList.contentType != 5  && setList.contentType != 6 && setList.contentType != 8 && setList.contentType != 9 && setList.contentType != 10  ">
+                                          <el-row>
+                                            <el-input type="number" v-model="setList.expiresDays"
+                                                      :disabled="formType == 3 || (form.sendType == 5 && contentIndex != 0 && setIndex == 0)"
+                                                      @change="updateAll(setIndex, item, 'expiresDays', $event)"
+                                                      style="width: 200px">
+                                              <template slot="append">天</template>
+                                            </el-input>
+                                          </el-row>
+                                          <el-row>
+                                            <span class="tip">填写0或不填时,默认为系统配置的默认时间</span>
+                                          </el-row>
+                                        </el-form-item>
+
+                                      </el-form>
+                                      <el-form v-if="form.sendType == 4" :model="setList" label-width="70px">
+                                        <el-form-item label="添加客服" prop="intervalTime" style="margin: 2%">
+                                          <el-input-number
+                                            v-model="setList.intervalTime"
+                                            :min="1"
+                                            :max="1440"
+                                            style="width:100px;margin-top: 10px;"
+                                          >
+                                          </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>
+                                    </el-col>
+                                    <el-col :span="1" :offset="1">
+                                      <i class="el-icon-delete" @click="delSetList(index,contentIndex,setIndex)"
+                                         style="margin-top: 20px;"
+                                         v-if="content.setting.length>1 && formType != 3 && !(form.sendType == 5 && setIndex == 0)"></i>
+                                    </el-col>
+                                  </el-row>
+                                </div>
+                                <el-link type="primary" class="el-icon-plus" :underline="false"
+                                         @click='addSetList(contentIndex,item.content)'
+                                         v-if="content.type != 4 && formType != 3">添加内容
+                                </el-link>
+                              </el-form>
+                            </el-col>
+                            <el-col :span="1" :offset="1">
+                              <i class="el-icon-delete" @click="delContent(index,contentIndex)"
+                                 style="margin-top: 20px;"
+                                 v-if="item.content.length>1 && formType != 3"></i>
+                            </el-col>
+                          </el-row>
+                        </div>
+                        <el-link type="primary" class="el-icon-plus" :underline="false" @click='addContent(index)'
+                                 v-if="formType != 3 && form.sendType != 4">添加规则
+                        </el-link>
+                      </el-form-item>
+                    </el-form>
+
+                  </div>
+                  <div style="float: right;" v-if="formType != 3">
+                    <el-button type="primary" @click="save" v-if="!item.voice || item.voice == 0">
+                      保存({{ '第' + (1 + (form.gap * index)) + '天' }})
+                    </el-button>
+                    <el-button type="primary" disabled v-if="item.voice == 1">语言生成中</el-button>
+                    <el-button type="primary" @click="leave(tabIndex)" v-if="item.voice == 1">刷新状态</el-button>
+                  </div>
+                </el-col>
+              </el-row>
+            </el-tab-pane>
+          </el-tabs>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <el-dialog :title="videoNumOptions.title" :visible.sync="videoNumOptions.open" style="width: 1500px;height: 100%"
+               append-to-body>
+      <userVideo ref="QwUserVideo" @videoResult="qwUserVideoResult"></userVideo>
+    </el-dialog>
+    <el-dialog title="修改天数排序" :visible.sync="openSort" style="width: 1500px" append-to-body>
+      <draggable v-model="dayList" @end="onDragEndDay" style="padding: 20px">
+        <el-button v-for="(item, index) in dayList" :class="item.newDay != item.dayNum ? 'red':''">第{{
+            item.newDay
+          }}天(第{{ item.dayNum }}天)
+        </el-button>
+      </draggable>
+
+      <div style="float: right;">
+        <el-button type="primary" @click="saveSorts">保存</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog title="修改规则排序" :visible.sync="openSort2" style="width: 1500px" append-to-body>
+      <draggable v-model="ruleList" @end="onDragEnd" style="padding: 20px">
+        <el-button v-for="(item, index) in ruleList">{{ item.time }}</el-button>
+      </draggable>
+
+      <div style="float: right;">
+        <el-button @click="autoSortsRules">自动排序</el-button>
+        <el-button type="primary" @click="saveSortsRules">保存</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+
+<style scoped>
+.el-button {
+  margin-left: 10px;
+}
+
+.red:hover {
+  color: #dbdbdb !important;
+}
+
+.red {
+  background-color: #F56C6C !important;
+  color: #fff !important;
+}
+
+.custom-input /deep/ .el-input__inner {
+  height: 20px;
+  text-align: center;
+}
+
+.custom-input /deep/ .el-input__icon {
+  line-height: 10px;
+}
+
+.el-icon-delete {
+  cursor: pointer;
+}
+
+/deep/ .el-badge__content.is-fixed {
+  top: 10px !important;
+  right: 0 !important;
+  width: 8px;
+}
+
+.tip {
+  color: #909399;
+  font-size: 12px;
+  margin-left: 10px;
+}
+
+.sortable-ghost {
+  background: rgb(26, 164, 255) !important;
+  color: #FFF !important;
+}
+</style>
+<script>
+import draggable from 'vuedraggable';
+import {
+  listSopTemp,
+  getSopTemp,
+  delSopTemp,
+  addSopTemp,
+  sortDay,
+  dayListFun,
+  updateSopTemp,
+  getSelectableRange,
+  exportSopTemp,
+  addOrUpdateSetting,
+  selectRulesInfo,
+  delRules
+} from "@/api/qw/sopTemp";
+import {courseList, videoList} from "@/api/qw/sop";
+import ImageUpload from "@/views/qw/sop/ImageUpload";
+import userVideo from "@/views/qw/userVideo/userVideo.vue";
+
+export default {
+  name: "updateSopTemp",
+  components: {ImageUpload, userVideo, draggable},
+  data() {
+    return {
+      addTag: [{
+        addTag: [],
+        inputVisible: false,
+        inputValue: '',
+        delTag: [],
+        delTagVisible: false,
+        delTagValue: ''
+      }],
+      uploadUrl: process.env.VUE_APP_BASE_API + "/common/uploadOSS",
+      uploadUrlByVoice: process.env.VUE_APP_BASE_API + "/common/uploadOSSByHOOKVoice",
+      //上传语音的遮罩层
+      voiceLoading: false,
+      openSort: false,
+      openSort2: false,
+      // 遮罩层
+      loading: false,
+      loading2: false,
+      loading3: false,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      dayList: [],
+      ruleList: [],
+      ids: [],
+      courseTypeList: ['3', '4', '9'],
+      sysFsSopWatchStatus: [],
+      //消息内容类型 企微版
+      sysQwSopContentType: [],
+      //插件版
+      sysQwSopAiContentType: [],
+
+      //类别
+      sysQwSopSettingType: [],
+
+      courseList: [],
+      videoList: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      defaultContentType: 1,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      id: "",
+      total: 0,
+      tabIndex: null,
+      // sop模板表格数据
+      setting: [],
+      data: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 状态字典
+      statusOptions: [],
+      startTimeRange: [],
+      videoNumOptions: {
+        title: '选择视频号',
+        open: false,
+        content: null,
+        contentIndex: null,
+        setIndex: null,
+      },
+      //1 修改 2 复制 3 查看
+      formType: null,
+      // 查询参数
+      form: {
+        name: null,
+        setting: null,
+        status: "1",
+        sort: 1,
+        companyId: null,
+        gap: 1,
+      },
+      // 表单校验
+      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'}
+        ],
+      }
+    };
+  },
+  created() {
+    this.getDicts("sys_qwSopAi_contentType").then(response => {
+      this.sysQwSopAiContentType = response.data;
+    });
+    this.getDicts("sys_fs_sop_watch_status").then(response => {
+      this.sysFsSopWatchStatus = response.data;
+    });
+
+    this.getDicts("sys_qwSop_contentType").then(response => {
+      this.sysQwSopContentType = response.data;
+    });
+
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+
+    this.getDicts("sys_qwSop_settingType").then(response => {
+      this.sysQwSopSettingType = response.data;
+    });
+
+    courseList().then(response => {
+      this.courseList = response.list;
+    });
+
+    getSelectableRange().then(e => {
+      this.startTimeRange = e.data;
+    })
+    const id = this.$route.params && this.$route.params.id;
+    this.id = id;
+    this.formType = this.$route.params && this.$route.params.type;
+    this.handleUpdate(id);
+  },
+  methods: {
+    handleClose(index, tag, content) {
+      content.addTag.splice(content.addTag.indexOf(tag), 1);
+    },
+    showInput(index) {
+      this.addTag[index].inputVisible = true;
+    },
+    handleInputConfirm(index, content) {
+      let inputValue = this.addTag[index].inputValue;
+      if (inputValue) {
+        if (!content.hasOwnProperty('addTag')) {
+          this.$set(content, 'addTag', []);
+        }
+        content.addTag.push(inputValue);
+      }
+      this.addTag[index].inputVisible = false;
+      this.addTag[index].inputValue = '';
+    },
+
+    // 检查字节长度
+    checkByteLength(content, type, isOfficial) {
+
+      if (type == 4 && isOfficial == '1')
+        for (let i = 0; i < content.setting.length; i++) {
+          const text = content.setting[i].miniprogramTitle;
+          const byteLength = this.getByteLength(text); // 获取当前字节数
+          // 如果字节数超过64,截断输入内容
+          if (byteLength > 64) {
+            this.$set(content.setting[i], 'miniprogramTitle', this.truncateTextByByteLength(text, 60));
+          }
+
+        }
+
+    },
+
+    // 计算字符串的字节数
+    getByteLength(text) {
+      return new Blob([text]).size; // 使用 Blob 计算字节数
+    },
+
+    // 根据字节数截断字符串
+    truncateTextByByteLength(text, maxByteLength) {
+      let byteLength = 0;
+      let result = "";
+
+      for (let i = 0; i < text.length; i++) {
+        const char = text[i];
+        const charByteLength = this.getByteLength(char); // 获取当前字符的字节数
+
+        // 如果加上当前字符的字节数后不超过限制,则添加到结果中
+        if (byteLength + charByteLength <= maxByteLength) {
+          result += char;
+          byteLength += charByteLength;
+        } else {
+          break; // 超过限制时停止
+        }
+      }
+
+      return result;
+    },
+
+    handleCloseDel(index, tag, content) {
+      content.delTag.splice(content.delTag.indexOf(tag), 1);
+    },
+
+    showInputDel(index) {
+      this.addTag[index].delTagVisible = true;
+    },
+    handleInputConfirmDel(index, content) {
+
+      let delTagValue = this.addTag[index].delTagValue;
+
+      if (delTagValue) {
+        if (!content.hasOwnProperty('delTag')) {
+          this.$set(content, 'delTag', []);
+        }
+        content.delTag.push(delTagValue);
+
+      }
+      this.addTag[index].delTagVisible = false;
+      this.addTag[index].delTagValue = '';
+    },
+    saveSorts() {
+      let list = this.dayList.filter(e => e.dayNum != e.newDay).map(e => {
+        return {dayNum: e.newDay, id: e.id}
+      })
+      this.loading3 = true;
+      sortDay(list).then(e => {
+        window.location.reload();
+      })
+    },
+    saveSortsRules() {
+      this.$set(this.setting[this.tabIndex], 'content', this.ruleList);
+      this.openSort2 = false;
+    },
+    autoSortsRules() {
+      this.ruleList = this.ruleList.sort((a, b) => {
+        // 将 time 字段转换为 Date 对象进行比较
+        const timeA = new Date(`1970-01-01T${a.time}Z`);
+        const timeB = new Date(`1970-01-01T${b.time}Z`);
+
+        // 比较时间顺序
+        return timeA - timeB; // 升序排序
+      });
+    },
+    openUpdateDaySorts() {
+      dayListFun(this.id).then(response => {
+        response.data.forEach((item) => item.newDay = item.dayNum);
+        this.dayList = response.data;
+        if (this.dayList == null || this.dayList.length == 0) {
+          this.$message.error("暂无天数")
+        } else {
+          this.openSort = true;
+        }
+      })
+    },
+    openUpdateSorts() {
+      this.ruleList = JSON.parse(JSON.stringify(this.setting[this.tabIndex].content));
+      if (this.ruleList == null || this.ruleList.length == 0) {
+        this.$message.error("暂无规则")
+      } else {
+        this.openSort2 = true;
+      }
+    },
+    onDragEndDay() {
+      this.dayList.forEach((item, index) => {
+        item.newDay = (this.form.gap * index) + 1;
+      })
+      this.$forceUpdate()
+    },
+    onDragEnd() {
+      // 更新排序值
+      // this.setting[this.tabIndex].content.forEach((item, index) => {
+      //   item.sorts = index;
+      // });
+    },
+    // 切换标签保存数据
+    leave(index, oldIndex) {
+      const newData = this.setting[index]
+      if (newData.dayNum && newData.id) {
+        this.loading = true;
+        selectRulesInfo(newData.id).then(res => {
+          this.$nextTick(() => {
+            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++) {
+
+              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);
+                }
+              }
+            }
+          })
+        });
+      }
+
+    },
+    save() {
+      let data = this.setting[this.tabIndex];
+      let check = this.checkData(data);
+      if (!check) {
+        return;
+      }
+      for (let j = 0; j < data.content.length; j++) {
+        if (data.content[j].type != 4 && data.content[j].type != 5) {
+          for (let k = 0; k < data.content[j].setting.length; k++) {
+            if (data.content[j].setting[k].contentType == 4 && (data.content[j].setting[k].miniprogramTitle != null || data.content[j].setting[k].miniprogramTitle != "")) {
+              data.content[j].setting[k].miniprogramTitle = this.truncateTextByByteLength(data.content[j].setting[k].miniprogramTitle, 60)
+            }
+          }
+        }
+      }
+
+      let index = 0;
+      const dataList = data.content.map(e => {
+        e.name = data.name;
+        e.tempId = this.id;
+        e.sorts = Number(this.tabIndex);
+        e.dayNum = (this.form.gap * e.sorts) + 1;
+        e.sorts = index++;
+        data.dayNum = e.dayNum;
+        e.contentType = e.type;
+        e.settingList = e.setting;
+        e.settingList.forEach(e => e.content = JSON.stringify(e));
+        if (e.addTag != null) {
+          e.addTag = JSON.stringify(e.addTag);
+        }
+        if (e.delTag != null) {
+          e.delTag = JSON.stringify(e.delTag);
+        }
+        return e;
+      })
+      data.tempId = this.id;
+      data.list = dataList;
+      this.loading = true;
+      addOrUpdateSetting(data).then(e => {
+        let content = this.setting[this.tabIndex].content;
+        for (let j = 0; j < content.length; j++) {
+          if (content[j].addTag != null) {
+            content[j].addTag = JSON.parse(content[j].addTag);
+          }
+          if (content[j].delTag != null) {
+            content[j].delTag = JSON.parse(content[j].delTag);
+          }
+        }
+        this.loading = false;
+        // 使用Vue.set确保响应式更新
+        this.$set(this.setting, this.tabIndex, {
+          ...this.setting[this.tabIndex],
+          id: e.data.id,
+        });
+      });
+    },
+    checkData(data) {
+      if (this.form.sendType != 4) {
+
+        for (let j = 0; j < data.content.length; j++) {
+          if (data.name == null || data.name == "") {
+            this.$message.error("内容名称不能为空")
+            return false;
+          }
+          if (data.content[j].time == null || data.content[j].time == "") {
+            this.$message.error("时间不能为空")
+            return false;
+          }
+          if (data.content[j].type == 2) {
+            if (data.content[j].courseId == null || data.content[j].courseId == "") {
+              this.$message.error("请选择课程")
+              return false;
+            }
+            if (data.content[j].videoId == null || data.content[j].videoId == "") {
+              this.$message.error("请选择课节")
+              return false;
+            }
+            if (data.content[j].courseType == null || data.content[j].courseType === "") {
+              this.$message.error("请选择消息类型")
+              return false;
+            }
+          }
+
+          if (data.content[j].type != 4 && data.content[j].type != 5) {
+            for (let k = 0; k < data.content[j].setting.length; k++) {
+              if (data.content[j].setting[k].contentType == 1 && (data.content[j].setting[k].value == null || data.content[j].setting[k].value == "")) {
+                this.$message.error("内容不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 2 && (data.content[j].setting[k].imgUrl == null || data.content[j].setting[k].imgUrl == "")) {
+                this.$message.error("图片不能为空")
+                return false;
+              }
+              if ((data.content[j].setting[k].contentType == 3 || data.content[j].setting[k].contentType == 9 || data.content[j].setting[k].contentType == 10) && (data.content[j].setting[k].linkTitle == null || data.content[j].setting[k].linkTitle == "")) {
+                this.$message.error("链接标题不能为空")
+                return false;
+              }
+              if ((data.content[j].setting[k].contentType == 3 || data.content[j].setting[k].contentType == 9 || data.content[j].setting[k].contentType == 10) && (data.content[j].setting[k].linkDescribe == null || data.content[j].setting[k].linkDescribe == "")) {
+                this.$message.error("链接描述不能为空")
+                return false;
+              }
+              if ((data.content[j].setting[k].contentType == 3 || data.content[j].setting[k].contentType == 9 || data.content[j].setting[k].contentType == 10) && (data.content[j].setting[k].linkImageUrl == null || data.content[j].setting[k].linkImageUrl == "")) {
+                this.$message.error("链接图片不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 3 && data.content[j].setting[k].type == 1 && (data.content[j].setting[k].linkUrl == null || data.content[j].setting[k].linkUrl == "")) {
+                this.$message.error("链接地址不能为空")
+                return false;
+              }
+
+              if (data.content[j].setting[k].contentType == 4 && (data.content[j].setting[k].miniprogramTitle == null || data.content[j].setting[k].miniprogramTitle == "")) {
+                this.$message.error("小程序消息标题不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 4 && data.content[j].isOfficial !== '1' && (data.content[j].setting[k].miniprogramPicUrl == null || data.content[j].setting[k].miniprogramPicUrl == "")) {
+                this.$message.error("小程序封面地址不能为空")
+                return false;
+              }
+
+              if (data.content[j].setting[k].contentType == 5 && (data.content[j].setting[k].fileUrl == null || data.content[j].setting[k].fileUrl == "")) {
+                this.$message.error("文件不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 6 && (data.content[j].setting[k].videoUrl == null || data.content[j].setting[k].videoUrl == "")) {
+                this.$message.error("视频不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 7 && (data.content[j].setting[k].value == null || data.content[j].setting[k].value == "")) {
+                this.$message.error("语音文本不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 8 && (data.content[j].setting[k].url == null || data.content[j].setting[k].url == "")) {
+                this.$message.error("视频号信息不能为空")
+                return false;
+              }
+            }
+          } else if (data.content[j].type == 4) {
+            if (data.content[j].aiTouch == null || data.content[j].aiTouch == '') {
+              this.$message.error("AI触达不能为空")
+              return false;
+            }
+          }
+        }
+      } else {
+        for (let day of this.setting) {
+          for (let content of day.content) {
+            for (let set of content.setting) {
+              if (!set.intervalTime) {
+                this.$message.error("客服数量不能为空");
+                return false;
+              }
+              if (!set.value) {
+                this.$message.error("内容不能为空");
+                return false;
+              }
+            }
+          }
+        }
+      }
+
+      return true;
+    },
+    tabClick(obj) {
+
+    },
+    addSetList(contentIndex, content) {
+
+      if (this.form.sendType == 1) {
+        for (let i = 0; i < content.length; i++) {
+
+          if (content[i].setting.length > 9) {
+            return this.$message.error("因为企微接口限制,企微模板一条消息只能设置最多~9个内容!!")
+          } else {
+
+            // 动态生成新的设置项
+            const newSetting = content[i].setting.some(item => item.contentType === '1')
+              ? {contentType: '2', imgUrl: ''}  // 如果存在 contentType = '1',添加 contentType = '2'
+              : {contentType: '1', value: ''};  // 如果不存在 contentType = '1',添加 contentType = '1'
+
+            // 将新设置项添加到 content[i].setting 数组中
+            content[i].setting.push(newSetting);
+          }
+
+        }
+      } else {
+
+        const newSetting = {
+          contentType: '1',
+          value: '',
+        };
+        if (this.form.sendType == 4) {
+          newSetting.intervalTime = 5;
+        }
+        // 将新设置项添加到 content.setting 数组中
+        content[contentIndex].setting.push(newSetting);
+      }
+
+
+    },
+    delSetList(index, contentIndex, setIndex) {
+      this.setting[index].content[contentIndex].setting.splice(setIndex, 1)
+
+    },
+
+    addContent(index) {
+      if (this.setting[index].content.length > 0 && this.form.sendType == 1) {
+        return this.$message.error("因为企微接口限制,企微模板一天只能设置一条消息~")
+      } else {
+        this.setting[index].content.push({type: this.defaultContentType, contentType: 1, setting: [{contentType: '1', value: "",}]})
+        this.videoList.push([])
+        this.addTag.push({
+          addTag: [],
+          inputVisible: false,
+          inputValue: '',
+          delTag: [],
+          delTagVisible: false,
+          delTagValue: ''
+        })
+      }
+
+    },
+    delContent(index, contentIndex) {
+      this.setting[index].content.splice(contentIndex, 1)
+      this.videoList.splice(contentIndex, 1)
+      this.addTag.splice(contentIndex, 1)
+    },
+    addSetting() {
+      let content = [{type: this.defaultContentType, contentType: '1', setting: [{contentType: '1', value: "",}]}];
+      if (this.form.sendType == 4) {
+        content[0].setting[0].intervalTime = 5;
+      }
+      this.setting.push({
+        name: null,
+        dayNum: (this.form.gap * this.setting.length) + 1,
+        content
+      })
+
+    },
+    delSetting(index) {
+      const newData = this.setting[index]
+      if (newData.id && newData.dayNum) {
+        this.$confirm('此操作将永久删除, 是否继续?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          delRules(newData.id, newData.name, newData.dayNum).then(res => {
+            this.del(index);
+          });
+        });
+      } else {
+        this.del(index);
+      }
+
+    },
+    del(index) {
+      if (index == 0) {
+        this.tabIndex = "0";
+      } else {
+        this.tabIndex = (index - 1) + "";
+      }
+      this.setting.splice(index, 1)
+
+    },
+    handleClosegroupUser(list) {
+      const index = this.userSelectList.findIndex(t => t === list);
+      if (index !== -1) {
+        this.userSelectList.splice(index, 1);
+      }
+
+    },
+    //选择视频号
+    hanldeSelectVideoNum(content, index, contentIndex, setIndex) {
+      this.videoNumOptions.content = content;
+      this.videoNumOptions.contentIndex = contentIndex;
+      this.videoNumOptions.setIndex = setIndex;
+      this.videoNumOptions.open = true;
+
+    },
+
+    qwUserVideoResult(val) {
+
+      // 根据选中的内容,将返回的数据更新到相应的表单项
+      const content = this.videoNumOptions.content;
+      const setList = content.setting[this.videoNumOptions.setIndex];
+
+      setList.nickname = val.nickname;
+      setList.avatar = val.avatar;
+      setList.coverUrl = val.coverUrl;
+      setList.thumbUrl = val.thumbUrl;
+      setList.desc = val.desc;
+      setList.url = val.url;
+      setList.extras = val.extras;
+
+      this.videoNumOptions.open = false;
+
+    },
+    //首次刷新
+    courseChange(content, index, countIndex) {
+
+      this.courseUpdate(content, index, countIndex);
+    },
+
+    //修改
+    courseChangeUpdate(content, index, countIndex) {
+
+      this.$set(content, 'videoId', null);
+      // 查找选中的课程对应的 label
+      const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
+      for (let i = 0; i < content.setting.length; i++) {
+
+
+        this.$set(content.setting[i], 'linkTitle', null);
+        this.$set(content.setting[i], 'linkDescribe', null);
+        this.$set(content.setting[i], 'linkImageUrl', null);
+
+
+        //如果是链接的才上
+        if (selectedCourse && content.type == 2 && content.courseId != null) {
+          //响应式直接给链接的标题/封面上值
+
+          if (content.setting[i].contentType == 3 || content.setting[i].contentType == 9) {
+            this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
+            this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+          }
+          if (content.setting[i].contentType == 4) {
+            this.$set(content.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+          }
+
+
+        }
+      }
+
+
+      this.courseUpdate(content, index, countIndex);
+    },
+    handleInputVideoText(value, content) {
+      // 允许的字符:中文、英文(大小写)、数字和指定标点符号(,。!?)
+      const regex = /^[\u4e00-\u9fa5,。!?,!?]+$/;
+
+      // 删除不符合条件的字符
+      const filteredValue = value.split('').filter(char => regex.test(char)).join('');
+
+      this.$set(content, 'value', filteredValue);
+
+    },
+    courseUpdate(content, index, countIndex) {
+      videoList(content.courseId).then(response => {
+        // this.$set(this.videoList, countIndex, response.list); // 响应式设置videoList
+        this.videoList.splice(countIndex, 1, response.list);
+
+      });
+
+    },
+    toggleSalesCall(itemIndex, contentIndex, setIndex) {
+      // 获取目标对象
+      const setItem = this.setting[itemIndex].content[contentIndex].setting[setIndex];
+      const salesCall = '#销售称呼#';
+      const refKey = `textarea-${itemIndex}-${contentIndex}-${setIndex}`;
+      const textarea = this.$refs[refKey][0]?.$refs?.textarea;
+
+      if (!textarea) return;
+
+      // 获取光标位置
+      const cursorPosition = textarea.selectionStart;
+
+      if (setItem.isSalesCallAdded) {
+        // 移除所有标签
+        setItem.value = setItem.value.replace(new RegExp(salesCall, 'g'), '');
+      } else {
+        // 插入到光标位置
+        setItem.value =
+          setItem.value.slice(0, cursorPosition) +
+          salesCall +
+          setItem.value.slice(cursorPosition);
+      }
+
+      // 切换状态
+      setItem.isSalesCallAdded = !setItem.isSalesCallAdded;
+
+      // 保持光标位置
+      this.$nextTick(() => {
+        const newCursorPos = cursorPosition + (setItem.isSalesCallAdded ? salesCall.length : 0);
+        textarea.setSelectionRange(newCursorPos, newCursorPos);
+      });
+    },
+    handleKeydown(event, itemIndex, contentIndex, setIndex) {
+
+      const setItem = this.setting[itemIndex].content[contentIndex].setting[setIndex];
+      const refKey = `textarea-${itemIndex}-${contentIndex}-${setIndex}`;
+      const textarea = this.$refs[refKey][0]?.$refs?.textarea;
+
+      if (!textarea) return;
+
+      const tags = ['#销售称呼#', '#客户称呼#'];
+      const key = event.key;
+      const value = setItem.value;
+      const cursorPosition = textarea.selectionStart;
+
+      if (key === 'Backspace' || key === 'Delete') {
+        tags.forEach(tag => {
+          let start, end;
+
+          // Backspace 处理
+          if (key === 'Backspace' && cursorPosition >= tag.length) {
+            start = cursorPosition - tag.length;
+            if (value.slice(start, cursorPosition) === tag) {
+              event.preventDefault();
+              setItem.value = value.slice(0, start) + value.slice(cursorPosition);
+              if (tag === '#销售称呼#') setItem.isSalesCallAdded = false;
+              this.$nextTick(() => textarea.setSelectionRange(start, start));
+            }
+          }
+
+          // Delete 处理
+          if (key === 'Delete') {
+            end = cursorPosition + tag.length;
+            if (value.slice(cursorPosition, end) === tag) {
+              event.preventDefault();
+              setItem.value = value.slice(0, cursorPosition) + value.slice(end);
+              if (tag === '#销售称呼#') setItem.isSalesCallAdded = false;
+            }
+          }
+        });
+      }
+    },
+    handleContentTypeChange(content, index, countIndex, setIndex, item, fieldName, val) {
+      //如果是链接的才上
+      if (content.courseId != null && content.type == 2) {
+        const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
+        for (let i = 0; i < content.setting.length; i++) {
+          //响应式直接给链接的标题/封面上值
+          if (selectedCourse && content.type == 2 && content.courseId != null) {
+            if (content.setting[i].contentType == 3 || content.setting[i].contentType == 9) {
+              this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
+              this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+            }
+            if (content.setting[i].contentType == 4 && (content.isOfficial == '0' || content.isOfficial == null)) {
+              console.log(content.isOfficial);
+              this.$set(content.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+            }
+
+          }
+
+        }
+
+      }
+      if (content.videoId != null && content.type == 2) {
+        // 查找选中的课节对应的 label
+        const selectedVideo = this.videoList[countIndex].find(course => parseInt(course.dictValue) === content.videoId);
+
+        for (let i = 0; i < content.setting.length; i++) {
+          //响应式直接给链接的描述上值
+          if (selectedVideo && content.type == 2 && content.videoId != null) {
+
+            if (content.setting[i].contentType == 3 || content.setting[i].contentType == 9) {
+              this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+            }
+            if (content.setting[i].contentType == 4) {
+              this.$set(content.setting[i], 'miniprogramTitle', this.truncateTextByByteLength(selectedVideo.dictLabel, 60));
+            }
+          }
+        }
+      }
+      if(countIndex == 0 && setIndex == 0){
+        this.updateAll(setIndex, item, fieldName, val);
+        for (let index in item.content) {
+          if(index != 0){
+            this.handleContentTypeChange(item.content[index], 0, index, 0, item, fieldName, val);
+          }
+        }
+      }
+    },
+    videoIdChange(content, index, countIndex) {
+
+      //选择了课程小节则 默认绑上
+      let selectedVideo;
+      // 查找选中的课程对应的 label
+      if (content.videoId != null) {
+        selectedVideo = this.videoList[countIndex].find(course => parseInt(course.dictValue) === content.videoId);
+      }
+      for (let i = 0; i < content.setting.length; i++) {
+        //课程消息-文本内容
+        if (content.setting[i].contentType == 1 && content.type == 2) {
+          this.$set(content.setting[i], 'isBindUrl', '1');
+        }
+        //如果是链接的才上
+        if (selectedVideo && content.type == 2 && content.videoId != null) {
+
+          if (content.setting[i].contentType == 3 || content.setting[i].contentType == 9) {
+            this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+          }
+          if (content.setting[i].contentType == 4) {
+            this.$set(content.setting[i], 'miniprogramTitle', this.truncateTextByByteLength(selectedVideo.dictLabel, 60));
+          }
+
+
+        }
+      }
+
+    },
+    //上传文件
+    handleAvatarSuccessFile(res, file, content) {
+      if (res.code === 200) {
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'fileUrl', res.url);
+      } else {
+        this.msgError(res.msg);
+      }
+
+    },
+    beforeAvatarUploadFile(file) {
+      const isLt1M = file.size / 1024 / 1024 < 10;
+      if (!isLt1M) {
+        this.$message.error('上传大小不能超过 10MB!');
+      }
+
+      return isLt1M;
+    },
+
+    //下载文件
+    downloadUrl(materialUrl) {
+      // 直接返回文件 URL
+      return materialUrl;
+    },
+
+    handleAvatarSuccessVideo(res, file, content) {
+      if (res.code == 200) {
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'videoUrl', res.url);
+      } else {
+        this.msgError(res.msg);
+      }
+
+    },
+    beforeAvatarUploadVideo(file) {
+      const isLt30M = file.size / 1024 / 1024 < 10;
+      const isMP4 = file.type === 'video/mp4';
+
+      if (!isMP4) {
+        this.$message.error('仅支持上传 MP4 格式的视频文件!');
+        return false;
+      }
+
+      if (!isLt30M) {
+        this.$message.error('上传大小不能超过 10MB!');
+        return false;
+      }
+
+      return true;
+    },
+
+    handleAvatarSuccessVoice(res, file, content) {
+      if (res.code == 200) {
+        // 创建 Audio 对象加载音频
+        const audio = new Audio(res.mp3Url);
+        audio.addEventListener('loadedmetadata', () => {
+          // 获取音频时长
+          this.$set(content, 'voiceDuration', Math.ceil(audio.duration));
+        });
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'voiceUrl', res.silkUrl);
+        this.$set(content, 'mp3Url', res.mp3Url);
+      } else {
+        this.msgError(res.msg);
+      }
+      this.voiceLoading = false;
+
+    },
+    beforeAvatarUploadVoice(file) {
+      return new Promise((resolve, reject) => {
+        const isLt10M = file.size / 1024 / 1024 < 10; // 假设语音文件大小限制为10MB
+        const isVoiceType = ['audio/mp3', 'audio/mpeg', 'audio/wav', 'audio/x-wav'].includes(file.type);
+
+        if (!isVoiceType) {
+          this.$message.error('仅支持上传 MP3, WAV, X-WAV 格式的语音文件!');
+          return reject(false); // 不允许继续上传
+        }
+
+        if (!isLt10M) {
+          this.$message.error('上传大小不能超过 10MB!');
+          return reject(false); // 不允许继续上传
+        }
+
+        // 使用 FileReader 读取文件
+        const reader = new FileReader();
+        reader.onload = (event) => {
+          const audio = new Audio(event.target.result);
+          audio.addEventListener('loadedmetadata', () => {
+            // 获取时长并保存
+            if (Math.ceil(audio.duration) > 30) {
+              this.$message.error('音频时长不能超过30秒!');
+              this.voiceLoading = false;
+              return reject(false); // 不允许继续上传
+            }
+            resolve(true); // 允许上传
+          });
+        };
+
+        reader.onerror = () => {
+          this.$message.error('无法读取音频文件!请上传正确的音频文件');
+          reject(false); // 不允许继续上传
+        };
+
+        this.voiceLoading = true;
+        reader.readAsDataURL(file); // 开始读取文件
+      });
+
+    },
+
+    /** 查询sop模板列表 */
+    getList() {
+      this.loading = true;
+      listSopTemp(this.queryParams).then(response => {
+        this.sopTempList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+
+    },
+    // 取消按钮
+    cancel() {
+      this.$store.dispatch("tagsView/delView", this.$route);
+      this.$router.replace('/qw/conversion/sopTemp')
+      // this.reset();
+
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        setting: null,
+        status: "0",
+        sort: null,
+        createTime: null,
+        createBy: null,
+        companyId: null
+      };
+      this.resetForm("form");
+
+    },
+
+
+    /** 修改按钮操作 */
+    handleUpdate(id) {
+      getSopTemp(id).then(response => {
+        this.videoList = []
+        this.form = response.data;
+        this.setting = this.form.list || [];
+
+        if (Array.isArray(this.setting)) {
+          this.setting.forEach(item => {
+            if (item && Array.isArray(item.content)) {
+              item.content.forEach(content => {
+                if (content && Array.isArray(content.setting)) {
+                  content.setting.forEach(setList => {
+                    if (setList && !Object.hasOwn(setList, 'isSalesCallAdded')) {
+                      this.$set(setList, 'isSalesCallAdded', false);
+                    }
+                  });
+                }
+              });
+            }
+          });
+        }
+        this.form.list.forEach(e => e.newDay = e.dayNum)
+        this.dayList = JSON.parse(JSON.stringify(this.form.list));
+        this.videoList.push([]);
+        this.addTag.push({
+          addTag: [],
+          inputVisible: false,
+          inputValue: '',
+          delTag: [],
+          delTagVisible: false,
+          delTagValue: ''
+        })
+      });
+
+    },
+    /** 提交按钮 */
+    submitForm() {
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除sop模板编号为"' + ids + '"的数据项?', "警告", {
+        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(() => {
+      });
+
+    },
+    updateHtml(val) {
+      val || val();
+
+    },
+    updateAll(setIndex, list, fieldName, newVal) {
+      if(this.form.sendType == 5 && setIndex == 0) {
+        console.info("更新数据", newVal)
+        for (let index in list.content) {
+          this.$set(list.content[index].setting[0], fieldName, newVal);
+        }
+      }
+    }
+  }
+};
+</script>

+ 906 - 0
src/views/qw/sopTemp/updateSopTemp2.vue

@@ -0,0 +1,906 @@
+<template>
+  <div class="app-container">
+
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 && formType==1 "> sop规则【修改企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 && formType==2 "> sop规则【复制企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 && formType==3 "> sop规则【查看企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==1 "> sop规则【修改AI插件】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==2 "> sop规则【复制AI插件】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==3 "> sop规则【查看AI插件】模板</div>
+
+    <div style="margin-top: 10px;margin-left: 50px;margin-right: 100px;margin-bottom: 60px;">
+      <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="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="规则" prop="setting">
+          <el-timeline >
+            <el-timeline-item :timestamp="'第'+(1+(form.gap*index))+'天'" :color="'#0bbd87'" placement="top" v-for="(item, index) in setting"  style="margin-top: 10px;">
+              <el-row>
+                <el-col :span="22">
+                  <div style="background-color: #fbfbfb;padding: 15px;  border: 1px solid #e6e6e6; margin-bottom: 20px;">
+                    <el-form :model="item"  label-width="80px">
+
+                      <el-form-item label="内容名称"  style="height: 50px;">
+                        <el-input v-model="item.name" placeholder="内容名称,仅内部可见" />
+                      </el-form-item>
+
+                      <el-form-item label="规则"  >
+                        <div v-for="(content, contentIndex) in item.content" style="background-color: #fdfdfd;padding: 15px;  border: 1px solid #e6e6e6; margin-bottom: 20px;">
+
+                          <el-row>
+
+                            <el-col el-col :span="22" >
+                              <el-form :model="content"  label-width="70px">
+                                <el-form-item label="时间" >
+                                  <el-time-picker
+                                    class="custom-input"
+                                    v-model="content.time"
+                                    value-format="HH:mm"
+                                    format="HH:mm"
+                                    :picker-options="{ selectableRange: '00:21:00 - 23:59:59' }"
+                                    placeholder="时间"
+                                    style="width: 100px;height: 20px;" >
+                                  </el-time-picker>
+                                </el-form-item>
+                                <el-form-item label="消息类别"  >
+                                  <el-radio-group v-model="content.type" @change="() => content.contentType = '1'" >
+                                    <el-radio
+                                      :label="1"
+                                    >普通</el-radio>
+                                    <el-radio
+                                      :label="2"
+                                    >课程</el-radio>
+                                    <el-radio
+                                      :label="3"
+                                    >订单</el-radio>
+                                    <el-radio
+                                      :label="4"
+                                    >AI触达</el-radio>
+                                  </el-radio-group>
+                                </el-form-item>
+
+                                <el-form-item label="课程"  v-if="content.type == 2 " required>
+                                  <el-select v-model="content.courseId" placeholder="请选择课程" style=" margin-right: 10px;" size="mini"  @change="courseChangeUpdate(content,index,contentIndex)">
+                                    <el-option
+                                      v-for="dict in courseList"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="parseInt(dict.dictValue)"
+                                    />
+                                  </el-select>
+                                  <el-select v-model="content.videoId" placeholder="请选择小节" size="mini" style=" margin-right: 10px;"  @change="videoIdChange(content,index,contentIndex)">
+                                    <el-option
+                                      v-for="dict in videoList[index][contentIndex]"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="parseInt(dict.dictValue)"
+                                    />
+                                  </el-select>
+                                  <el-select v-model="content.courseType" placeholder="请选择消息类型" size="mini" style=" margin-right: 10px;" v-if="content.type != 4 ">
+                                    <el-option
+                                      v-for="dict in sysFsSopWatchStatus"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="parseInt(dict.dictValue)"
+                                    />
+                                  </el-select>
+
+                                </el-form-item>
+                                <el-form-item label="Ai触达"  v-if="content.type == 4 ">
+                                  <el-select  v-model="content.aiTouch" placeholder="请选择Ai触达类型" size="mini" style=" margin-right: 10px;" v-if="content.type == 4 ">
+                                    <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>
+
+                                <div v-for="(setList, setIndex) in content.setting" :key="setIndex" style="background-color: #fdfdfd; border: 1px solid #e6e6e6; margin-bottom: 20px;" v-if="content.type != 4 ">
+                                  <el-row>
+                                    <el-col :span="22">
+                                      <el-form :model="setList" label-width="70px">
+                                        <el-form-item label="内容类别" style="margin: 2%">
+                                          <div v-if="form.sendType == 1 ">
+                                            <el-radio-group  v-model="setList.contentType" >
+                                              <!--                                              <el-radio  :label="item.dictValue" v-for="item in sysQwSopContentType">{{item.dictLabel}}</el-radio>-->
+                                              <el-radio
+                                                v-for="item in sysQwSopContentType"
+                                                :label="item.dictValue"
+                                                :disabled="item.dictValue === '1' && content.setting.some(s => s.contentType == '1')">
+                                                {{ item.dictLabel }}
+                                              </el-radio>
+                                            </el-radio-group>
+                                          </div>
+                                          <div v-if="form.sendType == 2 ">
+                                            <el-radio-group  v-model="setList.contentType" @change="handleContentTypeChange(content,index,contentIndex,setIndex)">
+                                              <el-radio   :label="item.dictValue" v-for="item in sysQwSopAiContentType">{{item.dictLabel}}</el-radio>
+                                            </el-radio-group>
+                                          </div>
+                                        </el-form-item>
+                                        <el-form-item label="内容"  >
+                                          <el-input v-if="setList.contentType == 1 " v-model="setList.value" type="textarea" :rows="3" placeholder="内容" style="width: 90%;margin-top: 10px;"/>
+                                            <ImageUpload v-if="setList.contentType == 2 " v-model="setList.imgUrl" type="image" :num="1" :width="150" :height="150" />
+                                          <div v-if="setList.contentType == 3 ">
+                                            <el-card class="box-card">
+                                              <el-form-item label="链接标题:"  label-width="100px" required >
+                                                <el-input v-model="setList.linkTitle" placeholder="请输入链接标题" style="width: 90%;"/>
+                                              </el-form-item>
+                                              <el-form-item label="链接描述:"   label-width="100px" required >
+                                                <el-input type="textarea" :rows="3" v-model="setList.linkDescribe" placeholder="请输入链接描述" style="width: 90%;margin-top: 1%;"/>
+                                              </el-form-item>
+                                              <el-form-item label="链接封面:"   label-width="100px" required>
+                                                <ImageUpload  v-model="setList.linkImageUrl" type="image" :num="1" :file-size="2" :width="150" :height="150" style="margin-top: 1%;" />
+                                              </el-form-item>
+                                              <div v-if="content.type != 2 " style="margin-top: 1%" >
+                                                <el-form-item label="链接地址:"  label-width="100px" required>
+                                                  <el-input v-model="setList.linkUrl" placeholder="请输入链接地址" style="width: 90%;"/>
+                                                </el-form-item>
+                                              </div>
+                                              <div v-if="content.type == 2 ">
+                                                <el-form-item label="链接地址:"  label-width="100px" >
+                                                  <el-tag type="warning" v-model="setList.isBindUrl = 1 ">选择的课程小节 即为卡片链接地址</el-tag>
+                                                </el-form-item>
+                                              </div>
+                                            </el-card>
+                                          </div>
+                                          <div v-if="setList.contentType == 5 ">
+
+                                            <el-form-item label="上传文件:" prop="fileUrl" label-width="100px">
+                                              <el-upload
+                                                v-model="setList.fileUrl"
+                                                class="avatar-uploader"
+                                                :action="uploadUrl"
+                                                :show-file-list="false"
+                                                :on-success="(res, file) => handleAvatarSuccessFile(res, file, setList)"
+                                                :before-upload="beforeAvatarUploadFile">
+                                                <i class="el-icon-plus avatar-uploader-icon"></i>
+                                              </el-upload>
+                                              <el-link v-if="setList.fileUrl" type="primary" :href="downloadUrl(setList.fileUrl)" download>
+                                                {{setList.fileUrl}}
+                                              </el-link>
+                                            </el-form-item>
+
+                                          </div>
+
+                                          <div v-if="setList.contentType == 6 ">
+                                            <el-form-item label="上传视频:" prop="videoUrl" label-width="100px">
+                                              <el-upload
+                                                v-model="setList.videoUrl"
+                                                class="avatar-uploader"
+                                                :action="uploadUrl"
+                                                :show-file-list="false"
+                                                :on-success="(res, file) => handleAvatarSuccessVideo(res, file, setList)"
+                                                :before-upload="beforeAvatarUploadVideo">
+                                                <i class="el-icon-plus avatar-uploader-icon"></i>
+                                              </el-upload>
+                                              <video v-if="setList.videoUrl"
+                                                     :src="setList.videoUrl"
+                                                     controls style="width: 200px;height: 100px">
+                                              </video>
+                                            </el-form-item>
+                                          </div>
+                                          <div v-if="setList.contentType == 7 ">
+                                              <el-input
+                                                v-model="setList.value"
+                                                type="textarea" :rows="3" maxlength="66" show-word-limit
+                                                placeholder="输入要转为语音的内容" style="width: 90%;margin-top: 10px;"
+                                                @input="handleInputVideoText(setList.value,setList)"/>
+
+
+                                          </div>
+                                          <div v-if="setList.contentType == 8 ">
+                                            <el-button type="primary" style="margin-bottom: 1%" @click="hanldeSelectVideoNum(content,index,contentIndex,setIndex)">选择视频号</el-button>
+                                            <el-card class="box-card" v-if="setList.coverUrl">
+                                              <el-form-item label="封面标题:"  label-width="100px">
+                                                <el-input v-model="setList.nickname" style="width: 90%;margin-bottom: 1%" disabled/>
+                                              </el-form-item>
+                                              <el-form-item label="头像:"   label-width="100px" >
+                                                <el-image
+                                                  v-if="setList.avatar != null"
+                                                  :src="setList.avatar"
+                                                  :preview-src-list="[setList.avatar]"
+                                                  :style="{ width: '50px', height: '50px' }"
+                                                ></el-image>
+                                              </el-form-item>
+                                              <el-form-item label="封面:"   label-width="100px" >
+                                                <el-image
+                                                  v-if="setList.coverUrl != null"
+                                                  :src="setList.coverUrl"
+                                                  :preview-src-list="[setList.coverUrl]"
+                                                  :style="{ width: '200px', height: '200px' }"
+                                                ></el-image>
+
+                                              </el-form-item>
+                                              <el-form-item label="简介:"   label-width="100px" >
+                                                <el-input type="textarea" :rows="3" v-model="setList.desc"  style="width: 90%;margin-top: 1%;" disabled />
+                                              </el-form-item>
+                                              <el-form-item label="视频地址:"  label-width="100px" style="margin-top: 1%" >
+                                                <el-input v-model="setList.url" style="width: 90%;" disabled />
+                                              </el-form-item >
+
+                                            </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
+                                              v-model="setList.isBindUrl"
+                                              :disabled="!content.videoId"
+                                              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 != 2  && setList.contentType != 5  && setList.contentType != 6 && setList.contentType != 8 ">
+                                          <el-row>
+                                            <el-input-number  v-model="setList.expiresDays"  :min="0" :max="100" ></el-input-number>
+                                            (天)
+                                          </el-row>
+                                          <el-row>
+                                            <span class="tip">填写0或不填时,默认为系统配置的默认时间</span>
+                                          </el-row>
+                                        </el-form-item>
+
+                                      </el-form>
+                                    </el-col>
+                                    <el-col :span="1" :offset="1">
+                                      <i class="el-icon-delete" @click="delSetList(index,contentIndex,setIndex)" style="margin-top: 20px;" v-if="content.setting.length>1 && (formType==1 || formType==2)"></i>
+                                    </el-col>
+                                  </el-row>
+                                </div>
+                                <el-link type="primary" class="el-icon-plus" :underline="false" @click='addSetList(contentIndex,item.content)' v-if="content.type != 4 && (formType==1 || formType==2)">添加内容</el-link>
+                              </el-form>
+
+
+                            </el-col>
+                            <el-col :span="1" :offset="1">
+                              <i class="el-icon-delete" @click="delContent(index,contentIndex)" style="margin-top: 20px;" v-if="item.content.length>1 && (formType==1 || formType==2)"></i>
+                            </el-col>
+                          </el-row>
+                        </div>
+                        <el-link type="primary" class="el-icon-plus" :underline="false" @click='addContent(index)' v-if="formType==1 || formType==2 ">添加规则</el-link>
+                      </el-form-item>
+                    </el-form>
+                  </div>
+                </el-col>
+                <el-col :span="1" :offset="1">
+                  <i class="el-icon-delete-solid" @click="delSetting(index)"  v-if="setting.length>1 && (formType==1 || formType==2)"></i>
+                </el-col>
+              </el-row>
+            </el-timeline-item>
+
+
+          </el-timeline>
+          <el-link type="primary" class="el-icon-plus" :underline="false" @click='addSetting()' v-if="formType==1 || formType==2">添加天数</el-link>
+        </el-form-item>
+
+      </el-form>
+      <div slot="footer" class="dialog-footer" style="float: right;" v-if="formType==1 || formType==2 ">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </div>
+
+    <el-dialog :title="videoNumOptions.title" :visible.sync="videoNumOptions.open" style="width: 1500px;height: 100%" append-to-body>
+      <userVideo ref="QwUserVideo" @videoResult="qwUserVideoResult" ></userVideo>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listSopTemp, getSopTemp, delSopTemp, addSopTemp, updateSopTemp, exportSopTemp } from "@/api/qw/sopTemp";
+import { courseList,videoList } from "@/api/qw/sop";
+import ImageUpload from "@/views/qw/sop/ImageUpload";
+import userVideo from "@/views/qw/userVideo/userVideo.vue";
+export default {
+  name: "updateSopTemp",
+  components: { ImageUpload,userVideo},
+  data() {
+    return {
+      uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS2",
+      uploadUrlByVoice:process.env.VUE_APP_BASE_API+"/common/uploadOSSByHOOKVoice",
+      //上传语音的遮罩层
+      voiceLoading :false,
+
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      sysFsSopWatchStatus: [],
+      //消息内容类型 企微版
+      sysQwSopContentType:[],
+      //插件版
+      sysQwSopAiContentType:[],
+
+      //类别
+      sysQwSopSettingType:[],
+
+      courseList:[],
+      videoList:[],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // sop模板表格数据
+      setting: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 状态字典
+      statusOptions: [],
+      videoNumOptions:{
+        title:'选择视频号',
+        open:false,
+        content:null,
+        contentIndex:null,
+        setIndex:null,
+      },
+      //1 修改 2 复制 3 查看
+      formType:null,
+      // 查询参数
+      form: {
+        name: null,
+        setting: null,
+        status: "1",
+        sort: 1,
+        companyId: null,
+        gap:1,
+      },
+      // 表单校验
+      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' }
+        ],
+      }
+    };
+  },
+  created() {
+    this.getDicts("sys_qwSopAi_contentType").then(response => {
+      this.sysQwSopAiContentType = response.data;
+    });
+    this.getDicts("sys_fs_sop_watch_status").then(response => {
+      this.sysFsSopWatchStatus = response.data;
+    });
+
+    this.getDicts("sys_qwSop_contentType").then(response => {
+      this.sysQwSopContentType = response.data;
+    });
+
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+
+    this.getDicts("sys_qwSop_settingType").then(response => {
+      this.sysQwSopSettingType = response.data;
+    });
+
+    courseList().then(response => {
+      this.courseList = response.list;
+    });
+
+    const id = this.$route.params && this.$route.params.id;
+    this.formType = this.$route.params && this.$route.params.type;
+    this.handleUpdate(id);
+  },
+  methods: {
+    addSetList(contentIndex,content){
+
+      if (this.form.sendType==1){
+        for (let i = 0; i < content.length; i++) {
+
+          if (content[i].setting.length > 9) {
+            return this.$message.error("因为企微接口限制,企微模板一条消息只能设置最多~9个内容!!")
+          } else {
+
+            // 动态生成新的设置项
+            const newSetting = content[i].setting.some(item => item.contentType === '1')
+              ? {contentType: '2', imgUrl: ''}  // 如果存在 contentType = '1',添加 contentType = '2'
+              : {contentType: '1', value: ''};  // 如果不存在 contentType = '1',添加 contentType = '1'
+
+            // 将新设置项添加到 content[i].setting 数组中
+            content[i].setting.push(newSetting);
+          }
+
+        }
+      } else {
+
+        const newSetting = {
+          contentType: '1',
+          value: '',
+        };
+
+        // 将新设置项添加到 content.setting 数组中
+        content[contentIndex].setting.push(newSetting);
+
+      }
+
+    },
+    delSetList(index, contentIndex, setIndex) {
+      this.setting[index].content[contentIndex].setting.splice(setIndex, 1)
+    },
+
+    addContent(index) {
+
+      if (this.setting[index].content.length > 0 && this.form.sendType == 1) {
+        return this.$message.error("因为企微接口限制,企微模板一天只能设置一条消息~")
+      } else {
+        this.setting[index].content.push({type: 1, contentType: 1, setting: [{contentType: '1', value: "",}]})
+        this.videoList[index].push([])
+      }
+
+    },
+    delContent(index, contentIndex) {
+      this.setting[index].content.splice(contentIndex, 1)
+      this.videoList[index].splice(contentIndex, 1)
+    },
+    addSetting() {
+      this.setting.push({name: null, content: [{type: 1, contentType: '1', setting: [{contentType: '1', value: "",}]}]})
+      this.videoList.push([])
+
+    },
+    delSetting(index) {
+      this.setting.splice(index, 1)
+      this.videoList.splice(index, 1)
+    },
+    handleClosegroupUser(list) {
+      const index = this.userSelectList.findIndex(t => t === list);
+      if (index !== -1) {
+        this.userSelectList.splice(index, 1);
+      }
+    },
+    //选择视频号
+    hanldeSelectVideoNum(content,index,contentIndex,setIndex){
+      this.videoNumOptions.content = content;
+      this.videoNumOptions.contentIndex = contentIndex;
+      this.videoNumOptions.setIndex = setIndex;
+      this.videoNumOptions.open=true;
+    },
+
+    qwUserVideoResult(val){
+
+      // 根据选中的内容,将返回的数据更新到相应的表单项
+      const content = this.videoNumOptions.content;
+      const setList = content.setting[this.videoNumOptions.setIndex];
+
+      setList.nickname = val.nickname;
+      setList.avatar = val.avatar;
+      setList.coverUrl = val.coverUrl;
+      setList.thumbUrl = val.thumbUrl;
+      setList.desc = val.desc;
+      setList.url = val.url;
+      setList.extras = val.extras;
+
+      this.videoNumOptions.open=false;
+    },
+    //首次刷新
+    courseChange(content, index, countIndex) {
+
+      this.courseUpdate(content, index, countIndex);
+    },
+
+    //修改
+    courseChangeUpdate(content, index, countIndex) {
+      this.$set(content, 'videoId', null);
+      // 查找选中的课程对应的 label
+      const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
+
+      for (let i = 0; i < content.setting; i++) {
+        this.$set(content.setting[i], 'linkTitle', null);
+        this.$set(content.setting[i], 'linkDescribe', null);
+        this.$set(content.setting[i], 'linkImageUrl', null);
+
+        //如果是链接的才上
+        if (content.setting[i].contentType == 3 && content.type == 2 && content.courseId != null) {
+
+          //响应式直接给链接的标题/封面上值
+          if (selectedCourse) {
+            this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
+            this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+          }
+        }
+      }
+
+
+      this.courseUpdate(content, index, countIndex);
+    },
+    handleInputVideoText(value,content){
+      // 允许的字符:中文、英文(大小写)、数字和指定标点符号(,。!?)
+      const regex = /^[\u4e00-\u9fa5,。!?,!?]+$/;
+
+      // 删除不符合条件的字符
+      const filteredValue = value.split('').filter(char => regex.test(char)).join('');
+
+      this.$set(content, 'value', filteredValue);
+
+    },
+    courseUpdate(content, index, countIndex) {
+      videoList(content.courseId).then(response => {
+
+        this.videoList[index].splice(countIndex, 1, response.list);
+
+      });
+    },
+    handleContentTypeChange(content, index, countIndex) {
+
+      //如果是链接的才上
+      if (content.courseId != null && content.type == 2) {
+        const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
+        for (let i = 0; i < content.setting.length; i++) {
+          //响应式直接给链接的标题/封面上值
+          if (selectedCourse && content.setting[i].contentType == 3 && content.type == 2 && content.courseId != null) {
+            this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
+            this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+          }
+
+        }
+
+      }
+      if (content.videoId != null && content.type == 2) {
+        // 查找选中的课节对应的 label
+        const selectedVideo = this.videoList[index][countIndex].find(course => parseInt(course.dictValue) === content.videoId);
+
+        for (let i = 0; i < content.setting.length; i++) {
+          //响应式直接给链接的描述上值
+          if (selectedVideo && content.setting[i].contentType == 3 && content.type == 2 && content.videoId != null) {
+            this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+          }
+        }
+      }
+
+    },
+    videoIdChange(content, index, countIndex) {
+      //选择了课程小节则 默认绑上
+      let selectedVideo;
+      // 查找选中的课程对应的 label
+      if (content.videoId != null) {
+        selectedVideo = this.videoList[index][countIndex].find(course => parseInt(course.dictValue) === content.videoId);
+      }
+      for (let i = 0; i < content.setting.length; i++) {
+        //课程消息-文本内容
+        if (content.setting[i].contentType == 1 && content.type == 2) {
+          this.$set(content.setting[i], 'isBindUrl', '1');
+        }
+        //如果是链接的才上
+        if (content.setting[i].contentType == 3 && content.type == 2 && content.videoId != null) {
+
+          //响应式直接给链接的描述上值
+          if (selectedVideo) {
+            this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+          }
+        }
+      }
+
+    },
+    //上传文件
+    handleAvatarSuccessFile(res, file, content) {
+      if (res.code === 200) {
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'fileUrl', res.url);
+      } else {
+        this.msgError(res.msg);
+      }
+    },
+    beforeAvatarUploadFile(file) {
+      const isLt1M = file.size / 1024 / 1024 < 10;
+      if (!isLt1M) {
+        this.$message.error('上传大小不能超过 10MB!');
+      }
+      return isLt1M;
+    },
+
+    //下载文件
+    downloadUrl(materialUrl) {
+      // 直接返回文件 URL
+      return materialUrl;
+    },
+
+    handleAvatarSuccessVideo(res, file, content) {
+      if (res.code == 200) {
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'videoUrl', res.url);
+      } else {
+        this.msgError(res.msg);
+      }
+    },
+    beforeAvatarUploadVideo(file) {
+      const isLt30M = file.size / 1024 / 1024 < 10;
+      const isMP4 = file.type === 'video/mp4';
+
+      if (!isMP4) {
+        this.$message.error('仅支持上传 MP4 格式的视频文件!');
+        return false;
+      }
+
+      if (!isLt30M) {
+        this.$message.error('上传大小不能超过 10MB!');
+        return false;
+      }
+
+      return true;
+    },
+
+    handleAvatarSuccessVoice(res, file, content) {
+      if (res.code == 200) {
+        // 创建 Audio 对象加载音频
+        const audio = new Audio(res.mp3Url);
+        audio.addEventListener('loadedmetadata', () => {
+          // 获取音频时长
+          this.$set(content, 'voiceDuration', Math.ceil(audio.duration));
+        });
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'voiceUrl', res.silkUrl);
+        this.$set(content, 'mp3Url', res.mp3Url);
+      } else {
+        this.msgError(res.msg);
+      }
+      this.voiceLoading = false;
+    },
+    beforeAvatarUploadVoice(file) {
+      return new Promise((resolve, reject) => {
+        const isLt10M = file.size / 1024 / 1024 < 10; // 假设语音文件大小限制为10MB
+        const isVoiceType = ['audio/mp3', 'audio/mpeg', 'audio/wav', 'audio/x-wav'].includes(file.type);
+
+        if (!isVoiceType) {
+          this.$message.error('仅支持上传 MP3, WAV, X-WAV 格式的语音文件!');
+          return reject(false); // 不允许继续上传
+        }
+
+        if (!isLt10M) {
+          this.$message.error('上传大小不能超过 10MB!');
+          return reject(false); // 不允许继续上传
+        }
+
+        // 使用 FileReader 读取文件
+        const reader = new FileReader();
+        reader.onload = (event) => {
+          const audio = new Audio(event.target.result);
+          audio.addEventListener('loadedmetadata', () => {
+            // 获取时长并保存
+            if (Math.ceil(audio.duration) > 30) {
+              this.$message.error('音频时长不能超过30秒!');
+              this.voiceLoading = false;
+              return reject(false); // 不允许继续上传
+            }
+            resolve(true); // 允许上传
+          });
+        };
+
+        reader.onerror = () => {
+          this.$message.error('无法读取音频文件!请上传正确的音频文件');
+          reject(false); // 不允许继续上传
+        };
+
+        this.voiceLoading = true;
+        reader.readAsDataURL(file); // 开始读取文件
+      });
+    },
+
+    /** 查询sop模板列表 */
+    getList() {
+      this.loading = true;
+      listSopTemp(this.queryParams).then(response => {
+        this.sopTempList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.$store.dispatch("tagsView/delView", this.$route);
+      this.$router.replace('/qw/conversion/sopTemp')
+      // this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        setting: null,
+        status: "0",
+        sort: null,
+        createTime: null,
+        createBy: null,
+        companyId: null
+      };
+      this.resetForm("form");
+    },
+
+
+    /** 修改按钮操作 */
+    handleUpdate(id) {
+
+      getSopTemp(id).then(response => {
+        this.videoList = []
+        this.form = response.data;
+        this.setting = JSON.parse(response.data.setting);
+        for (let i = 0; i < this.setting.length; i++) {
+          this.videoList.push([])
+          for (let j = 0; j < this.setting[i].content.length; j++) {
+            this.videoList[i].push([])
+            if (this.setting[i].content[j].type == 2) {
+              if (this.setting[i].content[j].hasOwnProperty('courseId')) {
+                this.courseChange(this.setting[i].content[j], i, j);
+              }
+            }
+          }
+        }
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+
+        this.form.setting = JSON.stringify(this.setting)
+
+        if (this.setting.length <= 0) {
+          return this.$message("请添加规则")
+        }
+
+        for (let i = 0; i < this.setting.length; i++) {
+
+
+          for (let j = 0; j < this.setting[i].content.length; j++) {
+
+            if (this.setting[i].name == null || this.setting[i].name == "") {
+              return this.$message.error("内容名称不能为空")
+            }
+            if (this.setting[i].content[j].time == null || this.setting[i].content[j].time == "") {
+              return this.$message.error("时间不能为空")
+            }
+            if (this.setting[i].content[j].type != 4) {
+              for (let k = 0; k < this.setting[i].content[j].setting.length; k++) {
+                if (this.setting[i].content[j].setting[k].contentType == 1 && (this.setting[i].content[j].setting[k].value == null || this.setting[i].content[j].setting[k].value == "")) {
+                  return this.$message.error("内容不能为空")
+                }
+                if (this.setting[i].content[j].setting[k].contentType == 2 && (this.setting[i].content[j].setting[k].imgUrl == null || this.setting[i].content[j].setting[k].imgUrl == "")) {
+                  return this.$message.error("图片不能为空")
+                }
+                if (this.setting[i].content[j].setting[k].contentType == 3 && (this.setting[i].content[j].setting[k].linkTitle == null || this.setting[i].content[j].setting[k].linkTitle == "")) {
+                  return this.$message.error("链接标题不能为空")
+                }
+                if (this.setting[i].content[j].setting[k].contentType == 3 && (this.setting[i].content[j].setting[k].linkDescribe == null || this.setting[i].content[j].setting[k].linkDescribe == "")) {
+                  return this.$message.error("链接描述不能为空")
+                }
+                if (this.setting[i].content[j].setting[k].contentType == 3 && (this.setting[i].content[j].setting[k].linkImageUrl == null || this.setting[i].content[j].setting[k].linkImageUrl == "")) {
+                  return this.$message.error("链接图片不能为空")
+                }
+                if (this.setting[i].content[j].setting[k].contentType == 3 && this.setting[i].content[j].setting[k].type == 1 && (this.setting[i].content[j].setting[k].linkUrl == null || this.setting[i].content[j].setting[k].linkUrl == "")) {
+                  return this.$message.error("链接地址不能为空")
+                }
+                if (this.setting[i].content[j].setting[k].contentType == 5 && (this.setting[i].content[j].setting[k].fileUrl == null || this.setting[i].content[j].setting[k].fileUrl == "")) {
+                  return this.$message.error("文件不能为空")
+                }
+                if (this.setting[i].content[j].setting[k].contentType == 6 && (this.setting[i].content[j].setting[k].videoUrl == null || this.setting[i].content[j].setting[k].videoUrl == "")) {
+                  return this.$message.error("视频不能为空")
+                }
+                if (this.setting[i].content[j].setting[k].contentType == 7 && (this.setting[i].content[j].setting[k].value == null || this.setting[i].content[j].setting[k].value == "")) {
+                  return this.$message.error("语音文本不能为空")
+                }
+                if (this.setting[i].content[j].setting[k].contentType == 8 && (this.setting[i].content[j].setting[k].url == null || this.setting[i].content[j].setting[k].url == "")) {
+                  return this.$message.error("视频号信息不能为空")
+                }
+              }
+            } else {
+              if (this.setting[i].content[j].aiTouch == null || this.setting[i].content[j].aiTouch == '') {
+                return this.$message.error("AI触达不能为空")
+              }
+            }
+          }
+
+
+        }
+        if (valid) {
+          if (this.formType == 1) {
+            if (this.form.id != null) {
+              updateSopTemp(this.form).then(response => {
+                this.msgSuccess("修改成功");
+                this.$store.dispatch("tagsView/delView", this.$route);
+                // this.$router.replace('/qw/conversion/sopTemp')
+                window.location.replace('/qw/conversion/sopTemp')
+                this.reset();
+
+              });
+            }
+          } else {
+            //id制空,防止键值重复-报错
+            this.form.id = null
+            //更新时间制空
+            this.form.updateTime = null
+            addSopTemp(this.form).then(response => {
+              this.msgSuccess("复制成功");
+              this.$store.dispatch("tagsView/delView", this.$route);
+              // this.$router.replace('/qw/conversion/sopTemp')
+              window.location.replace('/qw/conversion/sopTemp')
+              this.reset();
+
+            });
+          }
+
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除sop模板编号为"' + ids + '"的数据项?', "警告", {
+        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(() => {
+      });
+    }
+  }
+};
+</script>
+
+<style scoped>
+.custom-input /deep/ .el-input__inner {
+  height: 20px;
+  text-align: center;
+}
+
+.custom-input /deep/ .el-input__icon {
+  line-height: 10px;
+}
+</style>

+ 664 - 0
src/views/qw/sopTemp/updateSopTempOld.vue

@@ -0,0 +1,664 @@
+<template>
+  <div class="app-container">
+
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 "> sop规则【企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 "> sop规则【AI插件】模板</div>
+
+    <div style="margin-top: 10px;margin-left: 50px;margin-right: 100px;margin-bottom: 60px;">
+      <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="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="规则" prop="setting">
+          <el-timeline >
+            <el-timeline-item :timestamp="'第'+(1+(form.gap*index))+'天'" :color="'#0bbd87'" placement="top" v-for="(item, index) in setting"  style="margin-top: 10px;">
+              <el-row>
+                <el-col :span="22">
+                  <div style="background-color: #fbfbfb;padding: 15px;  border: 1px solid #e6e6e6; margin-bottom: 20px;">
+                    <el-form :model="item"  label-width="80px">
+
+                      <el-form-item label="内容名称"  style="height: 50px;">
+                        <el-input v-model="item.name" placeholder="内容名称,仅内部可见" />
+                      </el-form-item>
+
+                      <el-form-item label="规则"  >
+                        <div v-for="(content, contentIndex) in item.content" style="background-color: #fdfdfd;padding: 15px;  border: 1px solid #e6e6e6; margin-bottom: 20px;">
+
+                          <el-row>
+
+                            <el-col el-col :span="22" >
+                              <el-form :model="content"  label-width="70px">
+                                <el-form-item label="时间" >
+                                  <el-time-picker
+                                    class="custom-input"
+                                    v-model="content.time"
+                                    value-format="HH:mm"
+                                    format="HH:mm"
+                                    :picker-options="{ selectableRange: '00:00:00 - 23:59:59' }"
+                                    placeholder="时间"
+                                    style="width: 100px;height: 20px;" >
+                                  </el-time-picker>
+                                </el-form-item>
+                                <el-form-item label="消息类别"  >
+                                  <el-radio-group v-model="content.type" @change="() => content.contentType = '1'" >
+
+                                    <el-radio
+                                      :label="1"
+                                    >普通</el-radio>
+                                    <el-radio
+                                      :label="2"
+                                    >课程</el-radio>
+                                    <el-radio
+                                      :label="3"
+                                    >订单</el-radio>
+
+                                  </el-radio-group>
+                                </el-form-item>
+
+                                <el-form-item label="课程"  v-if="content.type == 2 " required>
+                                  <el-select v-model="content.courseId" placeholder="请选择课程" style=" margin-right: 10px;" size="mini"  @change="courseChangeUpdate(content,index,contentIndex)">
+                                    <el-option
+                                      v-for="dict in courseList"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="parseInt(dict.dictValue)"
+                                    />
+                                  </el-select>
+                                  <el-select v-model="content.videoId" placeholder="请选择小节" size="mini" style=" margin-right: 10px;"  @change="videoIdChange(content,index,contentIndex)">
+                                    <el-option
+                                      v-for="dict in videoList[index][contentIndex]"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="parseInt(dict.dictValue)"
+                                    />
+                                  </el-select>
+                                  <el-select v-model="content.courseType" placeholder="请选择消息类型" size="mini" style=" margin-right: 10px;">
+                                    <el-option
+                                      v-for="dict in sysFsSopWatchStatus"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="parseInt(dict.dictValue)"
+                                    />
+                                  </el-select>
+
+                                </el-form-item>
+                                <el-form-item label="内容类别"  >
+                                  <div v-if="form.sendType == 1 ">
+                                    <el-radio-group v-model="content.contentType">
+                                      <el-radio :label="item.dictValue" v-for="item in sysQwSopContentType">{{item.dictLabel}}</el-radio>
+                                    </el-radio-group>
+                                  </div>
+                                  <div v-if="form.sendType == 2 ">
+                                    <div v-if="content.type == 2 ">
+                                      <el-radio-group  v-model="content.contentType" @change="handleContentTypeChange(content,index,contentIndex)">
+                                        <el-radio  :label="item.dictValue" v-for="item in sysQwSopSettingType">{{item.dictLabel}}</el-radio>
+                                      </el-radio-group>
+                                    </div>
+                                    <div v-else>
+                                      <el-radio-group  v-model="content.contentType" @change="handleContentTypeChange(content,index,contentIndex)">
+                                        <el-radio   :label="item.dictValue" v-for="item in sysQwSopAiContentType">{{item.dictLabel}}</el-radio>
+                                      </el-radio-group>
+                                    </div>
+                                  </div>
+                                  <!--                                   <el-radio-group v-model="content.contentType">-->
+                                  <!--                                       <el-radio-->
+                                  <!--                                         :label="1"-->
+                                  <!--                                       >文字</el-radio>-->
+                                  <!--                                       <el-radio-->
+                                  <!--                                         :label="2"-->
+                                  <!--                                       >图片</el-radio>-->
+                                  <!--                                   </el-radio-group>-->
+                                </el-form-item>
+
+                                <el-form-item label="内容"  >
+                                  <el-input v-if="content.contentType == 1 " v-model="content.value" type="textarea" :rows="3" placeholder="内容" style="width: 90%;margin-top: 10px;"/>
+                                  <ImageUpload v-if="content.contentType == 2 " v-model="content.imgUrl" type="image" :num="1" :width="150" :height="150" />
+                                  <div v-if="content.contentType == 3 ">
+                                    <el-card class="box-card">
+                                      <el-form-item label="链接标题:"  label-width="100px" required >
+                                        <el-input v-model="content.linkTitle" placeholder="请输入链接标题" style="width: 90%;"/>
+                                      </el-form-item>
+                                      <el-form-item label="链接描述:"   label-width="100px" required >
+                                        <el-input type="textarea" :rows="3" v-model="content.linkDescribe" placeholder="请输入链接描述" style="width: 90%;margin-top: 1%;"/>
+                                      </el-form-item>
+                                      <el-form-item label="链接封面:"   label-width="100px" required>
+                                        <ImageUpload  v-model="content.linkImageUrl" type="image" :num="1" :width="150" :height="150" style="margin-top: 1%;" />
+                                      </el-form-item>
+                                      <div v-if="content.type != 2 " style="margin-top: 1%" >
+                                        <el-form-item label="链接地址:"  label-width="100px" required>
+                                          <el-input v-model="content.linkUrl" placeholder="请输入链接地址" style="width: 90%;"/>
+                                        </el-form-item>
+                                      </div>
+                                      <div v-if="content.type == 2 ">
+                                        <el-form-item label="链接地址:"  label-width="100px" >
+                                          <el-tag type="warning" v-model="content.isBindUrl = 1 ">选择的课程小节 即为卡片链接地址</el-tag>
+                                        </el-form-item>
+                                      </div>
+                                    </el-card>
+                                  </div>
+                                  <div v-if="content.contentType == 5 ">
+
+                                    <el-form-item label="上传文件:" prop="fileUrl" label-width="100px">
+                                      <el-upload
+                                        v-model="content.fileUrl"
+                                        class="avatar-uploader"
+                                        :action="uploadUrl"
+                                        :show-file-list="false"
+                                        :on-success="(res, file) => handleAvatarSuccessFile(res, file, content)"
+                                        :before-upload="beforeAvatarUploadFile">
+                                        <i class="el-icon-plus avatar-uploader-icon"></i>
+                                      </el-upload>
+                                      <el-link v-if="content.fileUrl" type="primary" :href="downloadUrl(content.fileUrl)" download>
+                                        {{content.fileUrl}}
+                                      </el-link>
+                                    </el-form-item>
+
+                                  </div>
+
+                                  <div v-if="content.contentType == 6 ">
+                                    <el-form-item label="上传视频:" prop="videoUrl" label-width="100px">
+                                      <el-upload
+                                        v-model="content.videoUrl"
+                                        class="avatar-uploader"
+                                        :action="uploadUrl"
+                                        :show-file-list="false"
+                                        :on-success="(res, file) => handleAvatarSuccessVideo(res, file, content)"
+                                        :before-upload="beforeAvatarUploadVideo">
+                                        <i class="el-icon-plus avatar-uploader-icon"></i>
+                                      </el-upload>
+                                      <video v-if="content.videoUrl"
+                                             :src="content.videoUrl"
+                                             controls style="width: 200px;height: 100px">
+                                      </video>
+                                    </el-form-item>
+                                  </div>
+                                  <div v-if="content.contentType == 7 ">
+                                    <el-form-item label="上传语音:" prop="videoUrl" label-width="100px">
+                                      <el-upload
+                                        v-model="content.voiceUrl"
+                                        class="avatar-uploader"
+                                        :action="uploadUrl"
+                                        :show-file-list="false"
+                                        :on-success="(res, file) => handleAvatarSuccessVoice(res, file, content)"
+                                        :before-upload="beforeAvatarUploadVoice">
+                                        <i class="el-icon-plus avatar-uploader-icon"></i>
+                                      </el-upload>
+                                      <audio  v-if="content.voiceUrl"
+                                              :src="content.voiceUrl"
+                                              controls style="width: 300px;height: 50px">
+                                      </audio >
+                                    </el-form-item>
+                                  </div>
+                                </el-form-item>
+                                <el-form-item label="添加短链" v-if="content.type == 2 && content.contentType == 1  "  >
+                                  <el-tooltip content="请先根据课程选定课程小节之后再添加" effect="dark" :disabled="!!content.videoId">
+                                    <el-switch
+                                      v-model="content.isBindUrl"
+                                      :disabled="!content.videoId"
+                                      active-color="#13ce66"
+                                      inactive-color="#DCDFE6"
+                                      active-value="1"
+                                      inactive-value="2">
+                                    </el-switch>
+                                  </el-tooltip>
+
+                                  <span v-if="content.isBindUrl == '1'" style="margin-left: 10px; color: #13ce66">添加URL</span>
+                                  <span v-if="content.isBindUrl == '2'" style="margin-left: 10px; color: #b1b4ba">不加URL</span>
+                                </el-form-item>
+                                <el-form-item label="课节过期时间" v-if="content.type == 2 && content.isBindUrl == '1' && content.contentType != 2  && content.contentType != 5  && content.contentType != 6 ">
+                                  <el-row>
+                                    <el-input-number  v-model="content.expiresDays"  :min="0" :max="100" ></el-input-number>
+                                    (天)
+                                  </el-row>
+                                  <el-row>
+                                    <span class="tip">填写0或不填时,默认为系统配置的默认时间</span>
+                                  </el-row>
+                                </el-form-item>
+                              </el-form>
+
+
+
+
+                            </el-col>
+                            <el-col :span="1" :offset="1">
+                              <i class="el-icon-delete" @click="delContent(index,contentIndex)" style="margin-top: 20px;" v-if="item.content.length>1"></i>
+                            </el-col>
+                          </el-row>
+                        </div>
+                        <el-link type="primary" class="el-icon-plus" :underline="false" @click='addContent(index)'>添加规则</el-link>
+                      </el-form-item>
+                    </el-form>
+                  </div>
+                </el-col>
+                <el-col :span="1" :offset="1">
+                  <i class="el-icon-delete-solid" @click="delSetting(index)"  v-if="setting.length>1"></i>
+                </el-col>
+              </el-row>
+            </el-timeline-item>
+
+
+          </el-timeline>
+          <el-link type="primary" class="el-icon-plus" :underline="false" @click='addSetting()'>添加天数</el-link>
+        </el-form-item>
+
+      </el-form>
+      <div slot="footer" class="dialog-footer" style="float: right;">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { listSopTemp, getSopTemp, delSopTemp, addSopTemp, updateSopTemp, exportSopTemp } from "@/api/qw/sopTemp";
+import { courseList,videoList } from "@/api/qw/sop";
+import ImageUpload from "@/views/qw/sop/ImageUpload";
+export default {
+  name: "SopTemp",
+  components: { ImageUpload},
+  data() {
+    return {
+      uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS",
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      sysFsSopWatchStatus: [],
+      //消息内容类型 企微版
+      sysQwSopContentType:[],
+      //插件版
+      sysQwSopAiContentType:[],
+
+      //类别
+      sysQwSopSettingType:[],
+
+      courseList:[],
+      videoList:[],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // sop模板表格数据
+      setting: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 状态字典
+      statusOptions: [],
+      // 查询参数
+      form: {
+        name: null,
+        setting: null,
+        status: "1",
+        sort: 1,
+        companyId: null,
+        gap:1,
+      },
+      // 表单校验
+      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' }
+        ],
+      }
+    };
+  },
+  created() {
+    this.getDicts("sys_qwSopAi_contentType").then(response => {
+      this.sysQwSopAiContentType = response.data;
+    });
+    this.getDicts("sys_fs_sop_watch_status").then(response => {
+      this.sysFsSopWatchStatus = response.data;
+    });
+
+    this.getDicts("sys_qwSop_contentType").then(response => {
+      this.sysQwSopContentType = response.data;
+    });
+
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+
+    this.getDicts("sys_qwSop_settingType").then(response => {
+      this.sysQwSopSettingType = response.data;
+    });
+
+    courseList().then(response => {
+      this.courseList = response.list;
+    });
+
+    const id = this.$route.params && this.$route.params.id;
+    this.handleUpdate(id);
+
+
+
+  },
+  methods: {
+    addContent(index){
+
+      if (this.setting[index].content.length>0 && this.form.sendType==1){
+        return this.$message.error("因为企微接口限制,企微模板一天只能设置一条消息~")
+      }else {
+        this.setting[index].content.push({type:1,contentType:1})
+        this.videoList[index].push([])
+      }
+
+    },
+    delContent(index,contentIndex){
+      this.setting[index].content.splice(contentIndex,1)
+      this.videoList[index].splice(contentIndex,1)
+    },
+    addSetting(){
+      this.setting.push({name:null,type:1,content:[],day:"0",hour:"0",minute:"0",time:""})
+      this.videoList.push([])
+
+    },
+    delSetting(index){
+      this.setting.splice(index,1)
+      this.videoList.splice(index,1)
+    },
+    handleClosegroupUser(list){
+      const index = this.userSelectList.findIndex(t => t === list);
+      if (index !== -1) {
+        this.userSelectList.splice(index, 1);
+      }
+    },
+
+    //首次刷新
+    courseChange(content,index,countIndex){
+
+      this.courseUpdate(content,index,countIndex);
+    },
+
+    //修改
+    courseChangeUpdate(content,index,countIndex){
+      this.$set(content, 'videoId', null);
+      this.$set(content, 'linkTitle', null);
+      this.$set(content, 'linkDescribe', null);
+      this.$set(content, 'linkImageUrl', null);
+      //如果是链接的才上
+      if (content.contentType==3 && content.type==2 && content.courseId!=null){
+
+        // 查找选中的课程对应的 label
+        const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
+
+        //响应式直接给链接的标题/封面上值
+        if (selectedCourse) {
+          this.$set(content, 'linkTitle', selectedCourse.dictLabel);
+          this.$set(content, 'linkImageUrl', selectedCourse.dictImgUrl);
+        }
+      }
+
+      this.courseUpdate(content,index,countIndex);
+    },
+
+    courseUpdate(content,index,countIndex){
+      videoList(content.courseId).then(response => {
+
+        this.videoList[index].splice(countIndex, 1, response.list);
+
+      });
+    },
+    handleContentTypeChange(content,index,countIndex){
+
+      //如果是链接的才上
+      if (content.contentType==3 && content.type==2 ) {
+
+        if (content.courseId!=null) {
+          // 查找选中的课程对应的 label
+          const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
+
+          //响应式直接给链接的标题/封面上值
+          if (selectedCourse) {
+            this.$set(content, 'linkTitle', selectedCourse.dictLabel);
+            this.$set(content, 'linkImageUrl', selectedCourse.dictImgUrl);
+          }
+        }
+        if (content.videoId!=null){
+          // 查找选中的课程对应的 label
+          const selectedVideo = this.videoList[index][countIndex].find(course => parseInt(course.dictValue) === content.videoId);
+
+          //响应式直接给链接的描述上值
+          if (selectedVideo) {
+            this.$set(content, 'linkDescribe', selectedVideo.dictLabel);
+          }
+        }
+
+      }
+    },
+    videoIdChange(content,index,countIndex){
+
+      //选择了课程小节则 默认绑上
+      this.$set(content,'isBindUrl','1');
+
+      //如果是链接的才上
+      if (content.contentType==3 && content.type==2 && content.videoId!=null) {
+
+        // 查找选中的课程对应的 label
+        const selectedVideo = this.videoList[index][countIndex].find(course => parseInt(course.dictValue) === content.videoId);
+
+        //响应式直接给链接的描述上值
+        if (selectedVideo) {
+          this.$set(content, 'linkDescribe', selectedVideo.dictLabel);
+        }
+      }
+
+    },
+    //上传文件
+    handleAvatarSuccessFile(res, file, content) {
+      if (res.code === 200) {
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'fileUrl', res.url);
+      } else {
+        this.msgError(res.msg);
+      }
+    },
+    beforeAvatarUploadFile(file){
+      const isLt1M = file.size / 1024 / 1024 < 30;
+      if (!isLt1M) {
+        this.$message.error('上传大小不能超过 30MB!');
+      }
+      return isLt1M;
+    },
+
+    //下载文件
+    downloadUrl(materialUrl) {
+      // 直接返回文件 URL
+      return materialUrl;
+    },
+
+    handleAvatarSuccessVideo(res, file, content) {
+      if(res.code==200){
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'videoUrl', res.url);
+      }
+      else{
+        this.msgError(res.msg);
+      }
+    },
+    beforeAvatarUploadVideo(file){
+      const isLt30M = file.size / 1024 / 1024 < 50;
+      const isMP4 = file.type === 'video/mp4';
+
+      if (!isMP4) {
+        this.$message.error('仅支持上传 MP4 格式的视频文件!');
+        return false;
+      }
+
+      if (!isLt30M) {
+        this.$message.error('上传大小不能超过 50MB!');
+        return false;
+      }
+
+      return true;
+    },
+
+    handleAvatarSuccessVoice(res, file, content){
+      if(res.code==200){
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'voiceUrl', res.url);
+      }
+      else{
+        this.msgError(res.msg);
+      }
+    },
+    beforeAvatarUploadVoice(file){
+      const isLt10M = file.size / 1024 / 1024 < 10; //语音文件大小限制为10MB
+      const isVoiceType = ['audio/mp3'].includes(file.type); // 支持的语音文件类型
+
+      if (!isVoiceType) {
+        this.$message.error('仅支持上传 MP3 格式的语音文件!');
+        return false;
+      }
+
+      if (!isLt10M) {
+        this.$message.error('上传大小不能超过 10MB!');
+        return false;
+      }
+
+      return true;
+    },
+    /** 查询sop模板列表 */
+    getList() {
+      this.loading = true;
+      listSopTemp(this.queryParams).then(response => {
+        this.sopTempList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.$store.dispatch("tagsView/delView", this.$route);
+      this.$router.replace('/qw/conversion/sopTemp')
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        setting: null,
+        status: "0",
+        sort: null,
+        createTime: null,
+        createBy: null,
+        companyId: null
+      };
+      this.resetForm("form");
+    },
+
+
+
+    /** 修改按钮操作 */
+    handleUpdate(id) {
+
+      getSopTemp(id).then(response => {
+        this.videoList=[]
+        this.form=response.data;
+        this.setting = JSON.parse(response.data.setting);
+        for (let i = 0; i < this.setting.length; i++) {
+          this.videoList.push([])
+          for (let j = 0; j < this.setting[i].content.length; j++) {
+            this.videoList[i].push([])
+            if(this.setting[i].content[j].type==2){
+              if(this.setting[i].content[j].hasOwnProperty('courseId')){
+                this.courseChange(this.setting[i].content[j],i,j);
+              }
+            }
+          }
+        }
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        this.form.setting=JSON.stringify(this.setting)
+        if (valid) {
+          if (this.form.id != null) {
+            updateSopTemp(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.$store.dispatch("tagsView/delView", this.$route);
+              // this.$router.replace('/qw/conversion/sopTemp')
+              window.location.replace('/qw/conversion/sopTemp')
+              this.reset();
+
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除sop模板编号为"' + ids + '"的数据项?', "警告", {
+        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(() => {});
+    }
+  }
+};
+</script>
+
+<style scoped>
+.custom-input /deep/ .el-input__inner {
+  height: 20px;
+  text-align:center;
+}
+.custom-input /deep/ .el-input__icon {
+  line-height: 10px;
+}
+</style>

+ 201 - 0
src/views/qw/sopTemp/updateTemp.vue

@@ -0,0 +1,201 @@
+<template>
+  <div class="app-container">
+
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 && formType==1 "> sop规则【修改企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 && formType==2 "> sop规则【复制企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 1 && formType==3 "> sop规则【查看企微接口】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==1 "> sop规则【修改AI插件】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==2 "> sop规则【复制AI插件】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==3 "> sop规则【查看AI插件】模板</div>
+
+    <div style="margin-top: 10px;margin-left: 50px;margin-right: 100px;margin-bottom: 60px;">
+      <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="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>
+      <div slot="footer" class="dialog-footer" style="float: right;" v-if="formType==1 || formType==2 ">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { listSopTemp, getSopTemp, delSopTemp, updateTemp, exportSopTemp } from "@/api/qw/sopTemp";
+export default {
+  name: "updateSopTemp",
+  data() {
+    return {
+      uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS2",
+      uploadUrlByVoice:process.env.VUE_APP_BASE_API+"/common/uploadOSSByHOOKVoice",
+      //上传语音的遮罩层
+      voiceLoading :false,
+
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      sysFsSopWatchStatus: [],
+      //消息内容类型 企微版
+      sysQwSopContentType:[],
+      //插件版
+      sysQwSopAiContentType:[],
+
+      //类别
+      sysQwSopSettingType:[],
+
+      courseList:[],
+      videoList:[],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // sop模板表格数据
+      setting: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 状态字典
+      statusOptions: [],
+      videoNumOptions:{
+        title:'选择视频号',
+        open:false,
+        content:null,
+        contentIndex:null,
+        setIndex:null,
+      },
+      //1 修改 2 复制 3 查看
+      formType:null,
+      // 查询参数
+      form: {
+        name: null,
+        setting: null,
+        status: "1",
+        sort: 1,
+        companyId: null,
+        gap:1,
+      },
+      // 表单校验
+      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' }
+        ],
+      }
+    };
+  },
+  created() {
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+    const id = this.$route.params && this.$route.params.id;
+    this.formType = this.$route.params && this.$route.params.type;
+    this.handleUpdate(id);
+  },
+  methods: {
+    // 取消按钮
+    cancel() {
+      this.$store.dispatch("tagsView/delView", this.$route);
+      this.$router.replace('/qw/conversion/sopTemp')
+      // this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        setting: null,
+        status: "0",
+        sort: null,
+        createTime: null,
+        createBy: null,
+        companyId: null
+      };
+      this.resetForm("form");
+    },
+
+
+    /** 修改按钮操作 */
+    handleUpdate(id) {
+      getSopTemp(id).then(response => {
+        this.form = response.data;
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.formType == 1) {
+            if (this.form.id != null) {
+              updateTemp(this.form).then(response => {
+                this.msgSuccess("修改成功");
+                this.$store.dispatch("tagsView/delView", this.$route);
+                // this.$router.replace('/qw/conversion/sopTemp')
+                window.location.replace('/qw/conversion/sopTemp')
+                this.reset();
+              });
+            }
+          } else {
+            //id制空,防止键值重复-报错
+            this.form.id = null
+            //更新时间制空
+            this.form.updateTime = null
+            addTemp(this.form).then(response => {
+              this.msgSuccess("复制成功");
+              this.$store.dispatch("tagsView/delView", this.$route);
+              // this.$router.replace('/qw/conversion/sopTemp')
+              window.location.replace('/qw/conversion/sopTemp')
+              this.reset();
+            });
+          }
+
+        }
+      });
+    },
+  }
+};
+</script>
+
+<style scoped>
+.custom-input /deep/ .el-input__inner {
+  height: 20px;
+  text-align: center;
+}
+
+.custom-input /deep/ .el-input__icon {
+  line-height: 10px;
+}
+</style>

+ 274 - 0
src/views/qw/userVideo/userVideo.vue

@@ -0,0 +1,274 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+        <el-alert
+          title="注意事项"
+          type="warning"
+          description="请先搜索相关【企业微信账号名称】,获取视频号信息"
+          :closable="false"
+          show-icon>
+        </el-alert>
+      <el-form-item label="企微账号名称" prop="qwUserName" >
+        <el-input
+          v-model="queryParams.qwUserName"
+          placeholder="请输入企微账号名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="封面标题" prop="nickname">
+        <el-input
+          v-model="queryParams.nickname"
+          placeholder="请输入封面标题"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <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="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+        >删除</el-button>
+      </el-col>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="QwUserVideoList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="封面图片" align="center" prop="coverUrl">
+        <template slot-scope="scope">
+          <el-popover
+            placement="right"
+            title=""
+            trigger="hover"
+          >
+            <img slot="reference" :src="scope.row.coverUrl" width="100" >
+            <img :src="scope.row.coverUrl" style="max-width: 300px;">
+          </el-popover>
+        </template>
+      </el-table-column>
+      <el-table-column label="头像" align="center" prop="avatar">
+        <template slot-scope="scope">
+          <el-popover
+            placement="right"
+            title=""
+            trigger="hover">
+            <img slot="reference" :src="scope.row.coverUrl" style="width: 50px;height: 50px">
+            <img :src="scope.row.coverUrl" style="max-width: 200px;max-height: 200px">
+          </el-popover>
+        </template>
+      </el-table-column>
+      <el-table-column label="封面标题" align="center" prop="nickname" />
+      <el-table-column label="简介" align="center" prop="desc" >
+        <template slot-scope="scope">
+          <el-tooltip class="item" effect="dark" :content="scope.row.desc" placement="top">
+            <div style="display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; text-overflow: ellipsis;">
+              <span>{{ scope.row.desc }}</span>
+            </div>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+      <el-table-column label="视频地址" align="center" prop="url">
+        <template slot-scope="scope">
+          <el-tooltip class="item" effect="dark" :content="scope.row.url" placement="top">
+            <div style="display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; text-overflow: ellipsis;">
+              <span>{{ scope.row.url }}</span>
+            </div>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-check"
+            @click="handleChangeVideoNum(scope.row)"
+          >选择此视频号</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"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listQwUserVideo, getQwUserVideo, delQwUserVideo, addQwUserVideo, updateQwUserVideo, exportQwUserVideo } from "@/api/qw/userVideo";
+
+export default {
+  name: "userVideo",
+  data() {
+    return {
+      // 遮罩层
+      loading: false,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 企业微信的视频号表格数据
+      QwUserVideoList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        appKey: null,
+        senderName: null,
+        qwUserName:null,
+        objectId: null,
+        coverUrl: null,
+        thumbUrl: null,
+        avatar: null,
+        nickname: null,
+        desc: null,
+        url: null,
+        extras: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+
+
+    /** 查询企业微信的视频号列表 */
+    getList() {
+      this.loading = true;
+      listQwUserVideo(this.queryParams).then(response => {
+        this.QwUserVideoList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        appKey: null,
+        senderName: null,
+        objectId: null,
+        coverUrl: null,
+        thumbUrl: null,
+        avatar: null,
+        nickname: null,
+        desc: null,
+        url: null,
+        extras: 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
+    },
+
+    handleChangeVideoNum(val){
+        this.$emit("videoResult",val)
+    },
+
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateQwUserVideo(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addQwUserVideo(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 delQwUserVideo(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有企业微信的视频号数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportQwUserVideo(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 22 - 22
src/views/qw/words/index.vue

@@ -10,7 +10,7 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-    
+
       <el-form-item label="操作人" prop="operator">
         <el-input
           v-model="queryParams.operator"
@@ -65,10 +65,10 @@
           v-hasPermi="['qw:words:export']"
         >导出</el-button>
       </el-col>
-	  <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table v-loading="loading" :data="wordsList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="wordsList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="序号"  width="55" type="index" align="center" />
       <el-table-column label="id" align="center" prop="id" />
@@ -93,7 +93,7 @@
         </template>
       </el-table-column>
     </el-table>
-    
+
     <pagination
       v-show="total>0"
       :total="total"
@@ -247,29 +247,29 @@ export default {
     handleDelete(row) {
       const ids = row.id || this.ids;
       this.$confirm('是否确认删除企微违禁词管理编号为"' + ids + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return delWords(ids);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("删除成功");
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delWords(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
     },
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有企微违禁词管理数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return exportWords(queryParams);
-        }).then(response => {
-          this.download(response.msg);
-        }).catch(function() {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return exportWords(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+      }).catch(function() {});
     }
   }
 };
-</script>
+</script>

+ 310 - 0
src/views/sop/companySopRole/index.vue

@@ -0,0 +1,310 @@
+<template>
+  <div class="app-container">
+<!--    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">-->
+<!--      <el-form-item label="销售公司" prop="companyId">-->
+<!--        <el-input-->
+<!--          v-model="queryParams.companyId"-->
+<!--          placeholder="请输入销售公司"-->
+<!--          clearable-->
+<!--          size="small"-->
+<!--          @keyup.enter.native="handleQuery"-->
+<!--        />-->
+<!--      </el-form-item>-->
+<!--      <el-form-item label="权限名称" prop="roleName">-->
+<!--        <el-input-->
+<!--          v-model="queryParams.roleName"-->
+<!--          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-button-->
+<!--          type="primary"-->
+<!--          plain-->
+<!--          icon="el-icon-plus"-->
+<!--          size="mini"-->
+<!--          @click="handleAdd"-->
+<!--          v-hasPermi="['sop:companySopRole: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="['sop:companySopRole: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="['sop:companySopRole: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="['sop:companySopRole:export']"-->
+<!--        >导出</el-button>-->
+<!--      </el-col>-->
+<!--    </el-row>-->
+
+    <el-table border v-loading="loading" :data="companySopRoleList">
+      <el-table-column label="销售公司" align="center" prop="companyId">
+        <template slot-scope="scope">
+          <el-tag v-for="item in companyList" v-if="item.companyId == scope.row.companyId"> {{scope.row.companyName}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="权限" align="center">
+        <template slot-scope="scope">
+          <div style="display: flex;flex-flow: wrap;justify-content: flex-start;">
+            <el-tag style="width: 30%;margin: 0 5px" v-for="item in scope.row.rules">{{roleMap[item]}}</el-tag>
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['sop:companySopRole:edit']"
+          >修改</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"
+    />
+
+    <!-- 添加或修改公司SOP权限对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="700px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="销售公司" prop="companyId">
+          <el-select filterable  v-model="form.companyId" placeholder="请选择公司名" size="small">
+            <el-option
+              v-for="item in companyList"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="权限" prop="rules">
+          <div style="margin: 15px 0;"></div>
+          <el-checkbox-group v-model="form.rules" style="display: flex;flex-flow: wrap;justify-content: flex-start;">
+            <el-checkbox style="width: 28%" v-for="item in roleOptions" :label="item.dictValue" :key="item.dictValue">{{item.dictLabel}}</el-checkbox>
+          </el-checkbox-group>
+        </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>
+</template>
+
+<script>
+import { listCompanySopRole, getCompanySopRole, delCompanySopRole, addCompanySopRole, updateCompanySopRole, exportCompanySopRole } from "@/api/sop/companySopRole";
+import { getCompanyList } from "@/api/company/company";
+
+export default {
+  name: "CompanySopRole",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 公司SOP权限表格数据
+      companySopRoleList: [],
+      roleOptions: [],
+      roleMap: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      companyList: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        companyId: null,
+        roleName: null,
+        roleValue: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.getDicts("company_sop_role").then(response => {
+      this.roleOptions = response.data;
+      this.roleMap = response.data.reduce((obj, item) => {
+        obj[item.dictValue] = item.dictLabel;
+        return obj;
+      }, {});
+    });
+    getCompanyList().then(response => {
+      this.companyList = response.data;
+    });
+  },
+  methods: {
+    /** 查询公司SOP权限列表 */
+    getList() {
+      this.loading = true;
+      listCompanySopRole(this.queryParams).then(response => {
+        console.info(response.rows)
+        this.companySopRoleList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        companyId: null,
+        roleName: null,
+        roleValue: null,
+        createTime: null,
+        createBy: null,
+        updateBy: null,
+        updateTime: null,
+        rules: this.roleOptions.map(e => e.dictValue),
+        remark: 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 = "添加公司SOP权限";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      if(row.rules && row.rules.length > 0){
+        this.form.rules = row.rules;
+      }
+      this.form.companyId = row.companyId;
+      this.open = true;
+      this.title = "修改公司SOP权限";
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateCompanySopRole(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addCompanySopRole(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除公司SOP权限编号为"' + ids + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delCompanySopRole(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有公司SOP权限数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportCompanySopRole(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+  }
+};
+</script>

+ 560 - 106
src/views/statistics/index.vue

@@ -4,16 +4,6 @@
     <el-card class="overview-section" shadow="never">
       <div slot="header" class="header">
         <span>数据概览</span>
-      <!--  <el-dropdown class="dropdown-menu" trigger="click">
-          <span class="el-dropdown-link">
-            部门方案查看 <i class="el-icon-arrow-down el-icon--right"></i>
-          </span>
-          <el-dropdown-menu slot="dropdown">
-            <el-dropdown-item>全部部门</el-dropdown-item>
-            <el-dropdown-item>销售部</el-dropdown-item>
-            <el-dropdown-item>市场部</el-dropdown-item>
-          </el-dropdown-menu>
-        </el-dropdown> -->
       </div>
 
       <el-row :gutter="20">
@@ -23,7 +13,8 @@
               <i class="el-icon-user-solid"></i>
               分公司数量
             </div>
-            <div class="card-value highlight">{{dealderCount}}</div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="dealderCount" :duration="3600" class="card-panel-num" /></div>
           </div>
         </el-col>
 
@@ -33,7 +24,9 @@
               <i class="el-icon-user"></i>
               销售数量
             </div>
-            <div class="card-value highlight">{{groupMgrCount}}</div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="groupMgrCount" :duration="3600" class="card-panel-num" />
+            </div>
           </div>
         </el-col>
 
@@ -43,7 +36,22 @@
               <i class="el-icon-shopping-cart-full"></i>
               会员数量
             </div>
-            <div class="card-value highlight">{{memberCount}}</div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="memberCount" :duration="3600" class="card-panel-num" />
+              <span class="highlight-today-add">+{{todayIncreaseUserNum}}</span>
+            </div>
+            <div class="card-badge">
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-shopping-cart-full"></i>
+              企微数量
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="memberCount" :duration="3600" class="card-panel-num" /></div>
             <div class="card-badge">
             </div>
           </div>
@@ -55,7 +63,9 @@
               <i class="el-icon-money"></i>
               可用余额
             </div>
-            <div class="card-value highlight">0.00</div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="balance" :duration="3600" class="card-panel-num" />
+            </div>
           </div>
         </el-col>
 
@@ -64,13 +74,17 @@
             <div class="card-title">
               <span>今日消耗</span>
             </div>
-            <div class="card-value highlight">0.00</div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="todayComsumption" :duration="3600" class="card-panel-num" />
+            </div>
             <div class="card-sub">
               <span>昨日消耗(元)</span>
-              <span class="sub-value">0.00</span>
+              <span class="sub-value">
+                <count-to :start-val="0" :end-val="yesterdayComsumption" :duration="3600" class="card-panel-num" />
+              </span>
             </div>
-            <el-progress :percentage="0" :show-text="false" color="#409EFF"></el-progress>
-            <div class="card-desc">预测不足0天</div>
+            <el-progress :percentage="percentage" :show-text="false" color="#409EFF"></el-progress>
+            <div class="card-desc">{{remainMessage}}</div>
           </div>
         </el-col>
 
@@ -80,10 +94,11 @@
               <span class="cdn-label">CDN</span>
               今日
             </div>
-            <div class="card-value highlight">0.00T</div>
+            <div class="card-value highlight">{{formatBytes(this.todayTraffic)}}
+            </div>
             <div class="card-sub">
               <span>本月</span>
-              <span class="sub-value">0.00T</span>
+              <span class="sub-value">{{formatBytes(this.thisMonthTraffic)}}</span>
             </div>
           </div>
         </el-col>
@@ -94,7 +109,9 @@
               <i class="el-icon-message"></i>
               短信剩余条数
             </div>
-            <div class="card-value highlight">{{smsRemainCount}}</div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="smsRemainCount" :duration="3600" class="card-panel-num" />
+            </div>
           </div>
         </el-col>
 
@@ -103,17 +120,101 @@
             <div class="card-title">
               平台今日看课人数
             </div>
-            <div class="card-value highlight">{{todayWatchUserCount}}</div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="todayWatchUserCount" :duration="3600" class="card-panel-num" />
+            </div>
             <div class="card-sub">
               <span>配额上限</span>
-              <span class="sub-value">{{todayWatchUserCount}}/{{versionLimit}}</span>
+              <span class="sub-value">
+                <count-to :start-val="0" :end-val="todayWatchUserCount" :duration="3600" class="card-panel-num" />/<count-to :start-val="0" :end-val="versionLimit" :duration="3600" class="card-panel-num" /></span>
+            </div>
+            <el-progress :percentage="todayWatchUserCount/versionLimit" :show-text="false" color="#409EFF"></el-progress>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-shopping-cart-full"></i>
+              订单总数
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="orderTotalNum" :duration="3600" class="card-panel-num" />
+              <span class="highlight-today-add">+{{todayOrderNum}}</span>
+            </div>
+            <div class="card-badge">
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-shopping-cart-full"></i>
+              收款总数
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="recvTotalNum" :duration="3600" class="card-panel-num" />
+              <span class="highlight-today-add">+{{recvTodayNum}}</span>
+            </div>
+            <div class="card-badge">
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-shopping-cart-full"></i>
+              商品总数
+            </div>
+            <div class="card-value highlight">
+              <count-to :start-val="0" :end-val="goodsTotalNum" :duration="3600" class="card-panel-num" />
+              <span class="highlight-today-add">+{{todayGoodsNum}}</span>
+            </div>
+            <div class="card-badge">
             </div>
-            <el-progress :percentage="70" :show-text="false" color="#409EFF"></el-progress>
           </div>
         </el-col>
       </el-row>
     </el-card>
 
+    <el-row :gutter="20" class="charts-section">
+      <el-col :span="12">
+        <el-card shadow="never">
+          <div slot="header" class="chart-header">
+            <span>本月订单数</span>
+            <div class="legend">
+              <div class="legend-item">
+                <span class="dot viewer-dot"></span>
+                <span>订单数</span>
+              </div>
+              <div class="legend-item">
+                <span class="dot complete-dot"></span>
+                <span>订单金额</span>
+              </div>
+            </div>
+          </div>
+          <div ref="viewerOrderChart" class="chart-container"></div>
+        </el-card>
+      </el-col>
+      <el-col :span="12">
+        <el-card shadow="never">
+          <div slot="header" class="chart-header">
+            <span>本月收款数</span>
+            <div class="legend">
+              <div class="legend-item">
+                <span class="dot viewer-dot"></span>
+                <span>收款数</span>
+              </div>
+              <div class="legend-item">
+                <span class="dot complete-dot"></span>
+                <span>收款金额</span>
+              </div>
+            </div>
+          </div>
+          <div ref="viewerReceiveChart" class="chart-container"></div>
+        </el-card>
+      </el-col>
+    </el-row>
+    <br/>
     <!-- 分析概览 (Analysis Overview) -->
     <div class="analysis-section" shadow="never">
       <div slot="header" class="header">
@@ -129,9 +230,25 @@
         </div>
 
         <div class="action-group">
-          <el-button size="small" plain icon="el-icon-refresh">手动刷新</el-button>
-          <el-button size="small" plain>自动刷新</el-button>
-          <el-button size="small" type="primary">刷新</el-button>
+          <el-radio-group v-model="userTypeText" @change="handleUserType">
+            <el-radio-button label="个微"></el-radio-button>
+            <el-radio-button label="企微"></el-radio-button>
+          </el-radio-group>
+
+          <el-dropdown @command="handleAutoRefresh" trigger="click">
+            <el-button size="small" plain>
+              自动刷新
+              <i class="el-icon-arrow-down el-icon--right"></i>
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item :command="0" :class="{ 'is-active': !autoRefreshInterval }">关闭</el-dropdown-item>
+              <el-dropdown-item :command="5" :class="{ 'is-active': autoRefreshInterval === 5 }">5分钟</el-dropdown-item>
+              <el-dropdown-item :command="10" :class="{ 'is-active': autoRefreshInterval === 10 }">10分钟</el-dropdown-item>
+              <el-dropdown-item :command="15" :class="{ 'is-active': autoRefreshInterval === 15 }">15分钟</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+          <el-button size="small" plain icon="el-icon-refresh" type="primary" @click="manualRefresh">手动刷新</el-button>
+<!--          <el-button size="small" type="primary" @click="refresh">刷新</el-button>-->
         </div>
       </div>
     </div>
@@ -144,11 +261,15 @@
               <div class="card-content">
                 <div class="card-row">
                   <span>观看人数</span>
-                  <span class="highlight">{{watchUserCount}}</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="watchUserCount" :duration="3600" class="card-panel-num" />
+                  </span>
                 </div>
                 <div class="card-row">
                   <span>完播人数</span>
-                  <span class="highlight">{{completedUserCount}}</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="completedUserCount" :duration="3600" class="card-panel-num" />
+                  </span>
                 </div>
                 <div class="card-row">
                   <span>完播率</span>
@@ -161,15 +282,18 @@
               <div class="card-content">
                 <div class="card-row">
                   <span>观看次数</span>
-                  <span class="highlight">{{watchCount}}</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="watchCount" :duration="3600" class="card-panel-num" /></span>
                 </div>
                 <div class="card-row">
                   <span>完播次数</span>
-                  <span class="highlight">{{completedCount}}</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="completedCount" :duration="3600" class="card-panel-num" />
+                  </span>
                 </div>
                 <div class="card-row">
                   <span>视频完播率</span>
-                  <span class="highlight">{{watchRate}}</span>
+                  <span class="highlight">{{watchRate}}%</span>
                 </div>
               </div>
             </div>
@@ -183,11 +307,15 @@
               <div class="card-content">
                 <div class="card-row">
                   <span>答题人数</span>
-                  <span class="highlight">{{answerMemberCount}}</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="answerMemberCount" :duration="3600" class="card-panel-num" />
+                  </span>
                 </div>
                 <div class="card-row">
                   <span>正确人数</span>
-                  <span class="highlight">{{correctUserCount}}</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="correctUserCount" :duration="3600" class="card-panel-num" />
+                    </span>
                 </div>
                 <div class="card-row">
                   <span>正确率</span>
@@ -205,11 +333,14 @@
               <div class="card-content">
                 <div class="card-row">
                   <span>答题红包个数</span>
-                  <span class="highlight">{{rewardCount}}</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="rewardCount" :duration="3600" class="card-panel-num" />
+                    </span>
                 </div>
                 <div class="card-row">
                   <span>答题红包金额(元)</span>
-                  <span class="highlight">{{rewardMoney}}</span>
+                  <span class="highlight">
+                    <count-to :start-val="0" :end-val="rewardMoney" :duration="3600" class="card-panel-num" /></span>
                 </div>
               </div>
             </div>
@@ -259,8 +390,8 @@
       </el-row>
     </transition>
     <transition name="fade">
-    <el-row>
-    <el-card shadow="never"  v-show="selectedDiv===1">
+    <el-row :gutter="20" class="charts-section" v-show="selectedDiv===1">
+    <el-card shadow="never">
       <div slot="header" class="chart-header">
         <span>课程观看TOP10</span>
         <div class="legend">
@@ -341,11 +472,12 @@
 
 <script>
 import * as echarts from 'echarts'
+import CountTo from "vue-count-to";
 import {
   analysisPreview,
   authorizationInfo,
-  dealerAggregated, deaMemberTopTen, rewardMoneyTopTen, rewardMoneyTrend,
-  smsBalance,
+  dealerAggregated, deaMemberTopTen, rechargeComsumption, rewardMoneyTopTen, rewardMoneyTrend,
+  smsBalance, thisMonthOrderCount, thisMonthRecvCount, trafficLog,
   watchCourseTopTen, watchEndPlayTrend
 } from "@/api/statistics/statistics";
 import dayjs from 'dayjs';
@@ -390,6 +522,86 @@ const viewCharOption = {
     }
   ]
 }
+
+const thisMonthOrderCountOption = {
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
+    }
+  },
+  grid: {
+    left: '3%',
+    right: '4%',
+    bottom: '3%',
+    containLabel: true
+  },
+  xAxis: {
+    type: 'category',
+    data: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23']
+  },
+  yAxis: {
+    type: 'value'
+  },
+  series: [
+    {
+      name: '订单数',
+      type: 'line',
+      data: [],
+      itemStyle: {
+        color: '#409EFF'
+      }
+    },
+    {
+      name: '订单金额',
+      type: 'line',
+      data: [],
+      itemStyle: {
+        color: '#67C23A'
+      }
+    }
+  ]
+}
+
+const thisMonthRecvCountOption = {
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
+    }
+  },
+  grid: {
+    left: '3%',
+    right: '4%',
+    bottom: '3%',
+    containLabel: true
+  },
+  xAxis: {
+    type: 'category',
+    data: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23']
+  },
+  yAxis: {
+    type: 'value'
+  },
+  series: [
+    {
+      name: '收款数',
+      type: 'line',
+      data: [],
+      itemStyle: {
+        color: '#409EFF'
+      }
+    },
+    {
+      name: '收款金额',
+      type: 'line',
+      data: [],
+      itemStyle: {
+        color: '#67C23A'
+      }
+    }
+  ]
+}
 const dealerOption = {
   tooltip: {
     trigger: 'axis',
@@ -497,14 +709,6 @@ const lineChartOption = {
     axisPointer: {
       type: 'cross' // 改为 'cross' 更适合折线图
     }
-    // 你可能想要自定义 tooltip 的 formatter 来显示时间和金额
-    // formatter: function (params) {
-    //   const point = params[0];
-    //   const date = new Date(point.value[0]);
-    //   // 自定义时间格式
-    //   const formattedTime = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
-    //   return `${point.seriesName}<br/>${formattedTime} : ${point.value[1]} 元`;
-    // }
   },
   grid: {
     left: '3%',
@@ -523,7 +727,6 @@ const lineChartOption = {
       // width: 100, // width 和 overflow 对于时间轴可能行为不同,按需调整
       // overflow: 'truncate',
       formatter: null // ECharts 会自动格式化时间,如需特定格式可用 function 或字符串模板
-      // 例如: formatter: '{yyyy}-{MM}-{dd}\n{HH}:{mm}'
     }
   },
   yAxis: {
@@ -547,15 +750,7 @@ const lineChartOption = {
     {
       name: '答题红包金额',
       type: 'line', // 系列类型改为 'line'
-      // data: [],  // 数据格式需要是 [[时间1, 金额1], [时间2, 金额2], ...]
-      // 时间可以是时间戳 (毫秒), 'YYYY-MM-DD HH:mm:ss' 格式字符串, 或者 Date 对象
-      // 例如: [['2023-10-26 08:00:00', 120], ['2023-10-26 09:00:00', 200]]
       data: [
-        // 示例数据,你需要用实际数据替换
-        // ['2024-01-01 10:00:00', 5.5],
-        // ['2024-01-01 11:00:00', 8.2],
-        // ['2024-01-02 09:30:00', 6.0],
-        // ['2024-01-03 14:00:00', 10.8],
       ],
       itemStyle: { // 控制数据点(标记)的样式
         color: '#409EFF'
@@ -617,13 +812,23 @@ const redPackageOption = {
 }
 export default {
   name: 'StatisticsDashboard',
+  components: {CountTo},
   data() {
     return {
+      percentage: 0,
+      // 预测message
+      remainMessage: '',
+      // 当天使用流量
+      todayTraffic: 0,
+      // 当月使用流量
+      thisMonthTraffic: 0,
       dataType: '0',
       delerSort: 'DESC',
       smsRemainCount: 0,
       viewerType: '0',
       viewerChart: null,
+      userTypeText: '个微',
+      userType: 1,
       dealerChart: null,
       // 分公司数量
       dealderCount: 0,
@@ -631,6 +836,8 @@ export default {
       groupMgrCount: 0,
       // 会员总数量
       memberCount: 0,
+      // 企微数量
+      qwMemberNum: 0,
       // 正常会员数量
       normalNum: 0,
       // 黑名单会员数量
@@ -663,7 +870,25 @@ export default {
       selectedDiv: 0,
       filterType: 0,
       answerRedPackViewerChart: null,
-      answerRedPackMoneyViewerChart: null
+      answerRedPackMoneyViewerChart: null,
+      todayComsumption: 0,
+      yesterdayComsumption: 0,
+      balance: 0,
+      autoRefreshInterval: null,
+      // 今日新增用户数
+      todayIncreaseUserNum: 0,
+      // 订单总数
+      orderTotalNum: 0,
+      // 今日新增订单数
+      todayOrderNum: 0,
+      // 收款总数
+      recvTotalNum: 0,
+      // 今日收款总数
+      recvTodayNum: 0,
+      // 商品总数
+      goodsTotalNum: 0,
+      // 今日商品总数
+      todayGoodsNum: 0
     }
   },
   mounted() {
@@ -673,6 +898,8 @@ export default {
       this.initCourseWatchChart();
       this.initAnswerRedPackViewerChart();
       this.initAnswerRedPackMoneyViewerChart();
+      this.initThisMonthOrderChart();
+      this.initThisMonthRecvChart()
 
       // 监听窗口大小变化,重新渲染图表
       window.addEventListener('resize', () => {
@@ -682,61 +909,227 @@ export default {
     })
   },
   created() {
-    dealerAggregated().then(res=>{
-      if(res.code === 200){
-        this.dealderCount = res.data.dealderCount;
-        this.groupMgrCount = res.data.groupMgrCount;
-        this.memberCount = res.data.memberCount;
-        this.normalNum = res.data.normalNum;
-        this.blackNum = res.data.blackNum;
+    this.refresh();
+  },
+  methods: {
+    handleUserType(){
+      if(this.userTypeText === '个微'){
+        this.userType = 1
+      }else{
+        this.userType = 2
       }
-    })
-    let param = {
-      startTime: '',
-      endTime: ''
-    };
-    // 获取当前日期时间
-    const today = dayjs();
-    param.startTime = this.formatDate(today);
-    param.endTime = this.formatDate(today);
-    analysisPreview(param).then(res=>{
-      if(res.code === 200){
-        this.watchUserCount = res.data.watchUserCount;
-        this.completedUserCount = res.data.completedUserCount;
-        this.completedRate = res.data.completedRate;
-        this.watchCount = res.data.watchCount;
-        this.completedCount = res.data.completedCount;
-        this.answerMemberCount = res.data.answerMemberCount;
-        this.correctUserCount = res.data.correctUserCount;
-        this.correctRate = res.data.correctRate;
-        this.rewardCount = res.data.rewardCount;
-        this.rewardMoney = res.data.rewardMoney;
+
+      this.refresh()
+    },
+    /**
+     * 计算余额预计可持续的天数
+     * @param {number} balance - 当前账户余额
+     * @param {number} todayConsumption - 今日消耗金额
+     * @param {number} yesterdayConsumption - 昨日消耗金额
+     * @return {Object} 包含天数和进度百分比的对象
+     */
+    calculateRemainingDays(balance, todayConsumption, yesterdayConsumption) {
+      // 如果今日和昨日消耗都为0,则无法预测(避免除以0)
+      if (todayConsumption === 0 && yesterdayConsumption === 0) {
+        return {
+          days: Infinity,
+          percentage: 0,
+          message: '暂无消耗数据'
+        };
       }
-    })
-    smsBalance().then(res=>{
-      if(res.code === 200){
-        this.smsRemainCount = res.data;
+
+      // 计算每日平均消耗量
+      const avgDailyConsumption = (todayConsumption + yesterdayConsumption) / 2;
+
+      // 如果平均消耗为0,则无法预测
+      if (avgDailyConsumption === 0) {
+        return {
+          days: Infinity,
+          percentage: 0,
+          message: '暂无消耗数据'
+        };
       }
-    })
-    authorizationInfo().then(res=>{
-      if(res.code === 200){
-        this.todayWatchUserCount = res.data.todayWatchUserCount;
-        this.versionLimit = res.data.versionLimit;
+
+      // 计算剩余天数(向下取整)
+      const remainingDays = Math.floor(balance / avgDailyConsumption);
+
+      // 计算进度条百分比,最大为100
+      // 这里假设100天是满值,可以根据需要调整
+      const maxDays = 100;
+      const percentage = Math.min(100, Math.max(0, Math.round((remainingDays / maxDays) * 100)));
+
+      let message = '';
+      if (remainingDays > 365) {
+        message = '预测余额充足';
+      } else {
+        message = `预测不足${remainingDays}天`;
+      }
+
+      return {
+        days: remainingDays,
+        percentage: 100 - percentage,
+        message: message
+      };
+    },
+    /**
+     * 将字节数转换为合适的单位表示(Byte、KB、MB、GB、TB)
+     * @param {number} bytes - 字节数
+     * @param {number} [decimals=2] - 小数点后保留的位数
+     * @returns {string} 格式化后的字符串,包含数值和单位
+     */
+    formatBytes(bytes, decimals = 2) {
+      if (bytes === 0) return '0 Byte';
+
+      const k = 1024;
+      const sizes = ['Byte', 'KB', 'MB', 'GB', 'TB'];
+
+      // 计算合适的单位级别
+      const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+      // 转换为对应单位的值
+      const value = bytes / Math.pow(k, i);
+
+      // 格式化为指定小数位的字符串
+      return parseFloat(value.toFixed(decimals)) + ' ' + sizes[Math.min(i, sizes.length - 1)];
+    },
+    // 手动刷新
+    manualRefresh() {
+      this.refresh();
+    },
+    // 处理自动刷新选项
+    handleAutoRefresh(command) {
+      // 清除之前的定时器
+      if (this.timer) {
+        clearInterval(this.timer);
+        this.timer = null;
       }
-    })
 
-    this.handleCourseWatchChart()
-    this.handleViewChartData()
+      // 设置新的刷新间隔
+      this.autoRefreshInterval = parseInt(command);
 
-    // 经销商会员观看TOP10
-    this.handleDealerChartData()
+      // 如果间隔大于0,设置新的定时器
+      if (this.autoRefreshInterval > 0) {
+        this.timer = setInterval(() => {
+          this.refresh();
+        }, this.autoRefreshInterval * 60 * 1000); // 转换为毫秒
 
-    this.handleAnswerRedPackViewerChart()
+        this.$message.success(`已设置${this.autoRefreshInterval}分钟自动刷新`);
+      } else {
+        this.$message.info('已关闭自动刷新');
+      }
+    },
+    refresh() {
+      rechargeComsumption().then(res=>{
+        if(res.code === 200){
+          this.balance = res.data.balance;
+          this.todayComsumption = res.data.todayComsumption;
+          this.yesterdayComsumption = res.data.yesterdayComsumption;
+          let calculateRemainingDays1 = this.calculateRemainingDays(this.balance,this.todayComsumption,this.yesterdayComsumption);
+          this.percentage = calculateRemainingDays1.percentage;
+          this.remainMessage = calculateRemainingDays1.message;
+        }
+      });
 
-    this.handleAnswerRedPackMoneyViewerChart()
+      trafficLog().then(res=>{
+        if(res.code === 200) {
+          this.todayTraffic = res.data.today;
+          this.thisMonthTraffic = res.data.thisMonth;
+        }
+      })
 
-  },
-  methods: {
+      dealerAggregated().then(res=>{
+        if(res.code === 200){
+          this.dealderCount = res.data.dealderCount??0;
+          this.groupMgrCount = res.data.groupMgrCount??0;
+          this.memberCount = res.data.memberCount??0;
+          this.qwMemberNum = res.data.qwMemberNum??0;
+          this.normalNum = res.data.normalNum??0;
+          this.blackNum = res.data.blackNum??0;
+          this.todayIncreaseUserNum = res.data.todayIncreaseUserNum??0;
+          this.orderTotalNum = res.data.orderTotalNum??0;
+          this.todayOrderNum = res.data.todayOrderNum??0;
+          this.recvTotalNum = res.data.recvTotalNum??0;
+          this.recvTodayNum = res.data.recvTodayNum??0;
+          this.goodsTotalNum = res.data.goodsTotalNum??0;
+          this.todayGoodsNum = res.data.todayGoodsNum??0;
+        }
+      })
+      let param = this.getParam();
+
+      // 获取当前日期时间
+      const today = dayjs();
+      param.startTime = this.formatDate(today);
+      param.endTime = this.formatDate(today);
+      analysisPreview(param).then(res=>{
+        if(res.code === 200){
+          this.watchUserCount = res.data.watchUserCount;
+          this.completedUserCount = res.data.completedUserCount;
+          this.completedRate = res.data.completedRate;
+          this.watchCount = res.data.watchCount;
+          this.completedCount = res.data.completedCount;
+          this.answerMemberCount = res.data.answerMemberCount;
+          this.correctUserCount = res.data.correctUserCount;
+          this.correctRate = res.data.correctRate;
+          this.rewardCount = res.data.rewardCount;
+          this.rewardMoney = res.data.rewardMoney;
+          this.watchRate = res.data.watchRate;
+        }
+      })
+      smsBalance().then(res=>{
+        if(res.code === 200){
+          if(res.data == null) {
+            this.smsRemainCount = 0;
+          } else {
+            this.smsRemainCount = res.data;
+          }
+        }
+      })
+      authorizationInfo().then(res=>{
+        if(res.code === 200){
+          this.todayWatchUserCount = res.data.todayWatchUserCount;
+          this.versionLimit = res.data.versionLimit;
+        }
+      })
+
+      this.handleCourseWatchChart()
+      this.handleViewChartData()
+
+      // 经销商会员观看TOP10
+      this.handleDealerChartData()
+
+      this.handleAnswerRedPackViewerChart()
+
+      this.handleAnswerRedPackMoneyViewerChart()
+
+      this.handleThisMonthRecvCount();
+      this.handleThisMonthOrderCount();
+
+    },
+    /**
+     * 将数字添加千位分隔符
+     * @param {number|string} num - 需要格式化的数字
+     * @return {string} 添加千位分隔符后的字符串
+     */
+     formatNumberWithCommas(num) {
+      if (num === null || num === undefined || isNaN(Number(num))) {
+        return '0';
+      }
+
+      const numStr = String(num);
+
+      // 处理负数
+      const isNegative = numStr.startsWith('-');
+      const absNumStr = isNegative ? numStr.slice(1) : numStr;
+
+      // 分离整数部分和小数部分
+      const parts = absNumStr.split('.');
+      const integerPart = parts[0];
+      const decimalPart = parts.length > 1 ? '.' + parts[1] : '';
+
+      const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+
+      return (isNegative ? '-' : '') + formattedInteger + decimalPart;
+    },
     handleToggleDiv(selected){
       this.selectedDiv = selected;
 
@@ -776,7 +1169,8 @@ export default {
     getParam(){
       let param = {
         startTime: '',
-        endTime: ''
+        endTime: '',
+        userType: this.userType
       };
       // 获取当前日期时间
       const today = dayjs();
@@ -829,6 +1223,7 @@ export default {
           this.correctRate = res.data.correctRate;
           this.rewardCount = res.data.rewardCount;
           this.rewardMoney = res.data.rewardMoney;
+          this.watchRate = res.data.watchRate;
         }
       })
 
@@ -912,6 +1307,35 @@ export default {
       })
 
     },
+    handleThisMonthOrderCount(){
+      thisMonthOrderCount().then(res=>{
+        if(res.code === 200){
+          let dates = res.dates;
+          let orderCount = res.orderCount;
+          let payPrice = res.payPrice;
+
+          thisMonthOrderCountOption.series[0].data = orderCount;
+          thisMonthOrderCountOption.series[1].data = payPrice;
+          thisMonthOrderCountOption.xAxis.data = dates;
+
+          this.thisMonthOrderChart.setOption(thisMonthOrderCountOption)
+        }
+      })
+    },
+    handleThisMonthRecvCount(){
+      thisMonthRecvCount().then(res=>{
+        if(res.code === 200){
+          let dates = res.dates;
+          let orderCount = res.orderCount;
+          let payMoney = res.payMoney;
+
+          thisMonthRecvCountOption.series[0].data = orderCount;
+          thisMonthRecvCountOption.series[1].data = payMoney;
+          thisMonthRecvCountOption.xAxis.data = dates;
+          this.thisMonthRecvChart.setOption(thisMonthRecvCountOption)
+        }
+      })
+    },
     handleViewChartData(){
       let param = this.getParam();
 
@@ -930,6 +1354,14 @@ export default {
       })
 
     },
+    initThisMonthOrderChart(){
+      this.thisMonthOrderChart = echarts.init(this.$refs.viewerOrderChart)
+      this.thisMonthOrderChart.setOption(thisMonthOrderCountOption)
+    },
+    initThisMonthRecvChart(){
+      this.thisMonthRecvChart = echarts.init(this.$refs.viewerReceiveChart)
+      this.thisMonthRecvChart.setOption(thisMonthOrderCountOption)
+    },
     initViewerChart() {
       this.viewerChart = echarts.init(this.$refs.viewerChart)
       this.viewerChart.setOption(viewCharOption)
@@ -957,7 +1389,12 @@ export default {
   },
 
   beforeDestroy() {
-    window.removeEventListener('resize', this.resizeHandler)
+    // 组件销毁时清除定时器
+    if (this.timer) {
+      clearInterval(this.timer);
+      this.timer = null;
+    }
+    // window.removeEventListener('resize', this.resizeHandler)
     this.viewerChart && this.viewerChart.dispose()
     this.dealerChart && this.dealerChart.dispose()
   }
@@ -965,6 +1402,17 @@ export default {
 </script>
 
 <style scoped>
+.highlight-today-add{
+  color:green;font-size:17px;font-weight: normal;
+}
+.action-group .el-button + .el-button,
+.action-group .el-dropdown {
+  margin-left: 10px;
+}
+.is-active {
+  color: #409EFF;
+  font-weight: bold;
+}
 ::v-deep .el-radio-button__inner:hover {
   color: #409EFF; /* 鼠标悬浮时的文字颜色,可以根据需要调整 */
 }
@@ -1012,6 +1460,11 @@ export default {
   display: flex;
   flex-direction: column;
   position: relative;
+  transition: background-color 0.3s ease-in-out;
+}
+.data-card:hover{
+  border: 1px solid #4592ff;
+  background-color: #e7f1ff;
 }
 
 .card-title {
@@ -1077,6 +1530,7 @@ export default {
   border-radius: 4px;
   padding: 20px;
   display: flex;
+  align-items: center;
 }
 
 .card-icon {

+ 649 - 0
src/views/statistics/member/index.vue

@@ -0,0 +1,649 @@
+<template>
+  <div class="member-statistics-page">
+  <!-- 会员统计页面容器 -->
+   <div class="member-statistics-container">
+    <!-- =================== 顶部查询区域 =================== -->
+      <el-form class="filter-container" :inline="true" :model="queryParams" ref="queryForm">
+        <div class="filter-item">
+          <!-- 按日/按月切换 -->
+          <el-radio-group v-model="queryParams.dateType" size="small" @input="handleChangeDateType">
+            <el-radio-button label="day">按每日</el-radio-button>
+            <el-radio-button label="month">按每月</el-radio-button>
+          </el-radio-group>
+        </div>
+
+        <div class="filter-item">
+          <!-- 日期选择器 -->
+          <el-date-picker
+            v-model="queryParams.dateRange"
+            :key="queryParams.dateType"
+            :type="queryParams.dateType === 'day' ? 'daterange' : 'monthrange'"
+            size="small"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            style="width: 260px;"
+            :clearable="false"
+          />
+        </div>
+
+        <!-- 经销商选择 -->
+        <el-form-item class="filter-item" label="经销商" prop="dealerId">
+          <!-- 经销商选择 -->
+          <el-select
+            v-model="queryParams.dealerId"
+            placeholder="请选择经销商"
+            size="small"
+            clearable filterable remote
+            :remote-method="loadCompanyOptions"
+            v-select-load-more="loadMoreCompanyOptions"
+            :loading="companyOptionsLoading"
+            @change="handleChangeDealer"
+            style="width: 140px;"
+          >
+            <el-option v-for="item in dealerOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="filter-item" label="群管" prop="groupId">
+          <!-- 分组选择 -->
+          <el-select
+            v-model="queryParams.groupId"
+            placeholder="请选择群管"
+            size="small"
+            clearable filterable remote
+            :remote-method="loadCompanyUserOptions"
+            v-select-load-more="loadMoreCompanyUserOptions"
+            :loading="companyUserOptionsLoading"
+            style="width: 140px;"
+          >
+            <el-option v-for="item in groupOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="filter-item" label="会员" prop="memberId">
+          <!-- 会员选择 -->
+          <el-select
+            v-model="queryParams.memberId"
+            placeholder="请选择会员"
+            size="small"
+            clearable filterable remote
+            :remote-method="loadUserOptions"
+            v-select-load-more="loadMoreUserOptions"
+            :loading="userOptionsLoading"
+            style="width: 140px;"
+          >
+            <el-option v-for="item in memberOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="filter-item" label="训练营" prop="trainerId">
+          <!-- 训练营选择 -->
+          <el-select
+            v-model="queryParams.trainerId"
+            placeholder="请选择训练营"
+            size="small"
+            clearable filterable remote
+            :remote-method="loadTrainerOptions"
+            v-select-load-more="loadMoreTrainerOptions"
+            :loading="trainerOptionsLoading"
+            @change="changeTrainer"
+            style="width: 140px;"
+          >
+            <el-option v-for="item in trainerOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="filter-item" label="营期" prop="campPeriod">
+          <!-- 营期选择 -->
+          <el-select
+            v-model="queryParams.campPeriod"
+            placeholder="请选择营期"
+            size="small"
+            clearable filterable remote
+            :remote-method="loadPeriodOptions"
+            v-select-load-more="loadMorePeriodOptions"
+            :loading="campPeriodOptionsLoading"
+            @change="changeCampPeriod"
+            style="width: 140px;"
+          >
+            <el-option v-for="item in campPeriodOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="filter-item" label="课程" prop="courseId">
+          <!-- 课程选择 -->
+          <el-select
+            v-model="queryParams.courseId"
+            placeholder="请选择课程"
+            size="small"
+            clearable filterable remote
+            :remote-method="loadCourseOptions"
+            v-select-load-more="loadMoreCourseOptions"
+            :loading="courseOptionsLoading"
+            style="width: 140px;"
+          >
+            <el-option v-for="item in courseOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="filter-item" label="手机号" prop="mobile">
+          <!-- 手机号输入框 -->
+          <el-input
+            v-model="queryParams.mobile"
+            placeholder="请输入手机号"
+            size="small"
+            clearable
+            style="width: 200px;"
+          />
+        </el-form-item>
+
+        <div class="filter-item">
+         <!-- 操作按钮组 -->
+         <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
+        </div>
+
+        <div class="filter-item" style="margin-left: auto; display: none;">
+         <el-button size="small">更换所属群管</el-button>
+         <el-button type="primary" size="small">导出全部</el-button>
+        </div>
+      </el-form>
+    </div>
+
+    <!-- =================== 表格数据展示区域 =================== -->
+    <div class="table-container">
+      <el-table
+        v-loading="loading"
+        :data="tableData"
+        border
+        style="width: 100%;"
+        show-summary
+        :summary-row-style="{ background: '#fafafa' }"
+      >
+        <!-- 选择列 -->
+        <el-table-column type="selection" width="50" align="center" />
+        <!-- 序号列 -->
+        <el-table-column label="序号" type="index" width="60" align="center" />
+        <!-- 统计日期列 -->
+        <el-table-column prop="date" label="统计日期" min-width="100" align="center" />
+        <!-- 会员名称列 -->
+        <el-table-column prop="memberName" label="会员名称" min-width="100" align="center" />
+        <!-- 性别列 -->
+        <el-table-column prop="gender" label="性别" width="60" align="center" />
+        <!-- 手机号列 -->
+        <el-table-column prop="mobile" label="手机号" min-width="100" align="center" />
+        <!-- 会员标签列 -->
+        <el-table-column prop="memberTag" label="会员标签" min-width="100" align="center" />
+        <!-- 所属群管列 -->
+        <el-table-column prop="groupName" label="所属群管" min-width="100" align="center" />
+        <!-- 所属经销商列 -->
+        <el-table-column prop="dealerName" label="所属经销商" min-width="100" align="center" />
+        <!-- 观看课程列 -->
+        <el-table-column prop="viewCount" label="观看课程" min-width="80" align="center" />
+        <!-- 完课课程列 -->
+        <el-table-column prop="finishCount" label="完课课程" min-width="80" align="center" />
+        <!-- 完课率列 -->
+        <el-table-column prop="finishRate" label="完课率" min-width="80" align="center" />
+        <!-- 观看次数列 -->
+        <el-table-column prop="viewTimes" label="观看次数" min-width="80" align="center" />
+        <!-- 完课次数列 -->
+        <el-table-column prop="finishTimes" label="完课次数" min-width="80" align="center" />
+        <!-- 视频率列 -->
+        <el-table-column prop="videoRate" label="视频率" min-width="80" align="center" />
+
+        <!-- 操作列 -->
+        <el-table-column label="操作" fixed="right" min-width="160" align="center">
+          <template slot-scope="scope">
+            <!-- 会员分析按钮 -->
+            <el-button type="text" size="small" style="color: #409EFF;">会员分析</el-button>
+            <!-- 红包记录按钮 -->
+            <el-button type="text" size="small" style="color: #409EFF;">红包记录</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="queryParams.total > 0"
+        :total="queryParams.total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import {dailyData} from '@/api/statistics/member'
+import {getCompanyListLikeName} from '@/api/company/company'
+import {getCompanyUserListLikeName} from '@/api/company/companyUser'
+import {getUserListLikeName} from '@/api/users/user'
+import {getCampListLikeName} from '@/api/course/userCourseCamp'
+import {getPeriodListLikeName} from '@/api/course/userCoursePeriod'
+import {getVideoListLikeName} from '@/api/course/userCourseVideo'
+import moment from "moment"
+
+export default {
+  // 组件名称
+  name: 'MemberStatistics',
+
+  // 组件数据
+  data() {
+    return {
+      // ============= 查询参数 =============
+      queryParams: {
+        dateType: 'day', // 日期类型:day-按日,month-按月
+        dateRange: [new Date(), new Date()], // 日期范围
+        dealerId: '', // 经销商ID
+        groupId: '', // 群组ID
+        memberId: '', // 会员ID
+        trainerId: '', // 训练官ID
+        campPeriod: '', // 营期
+        courseId: '', // 课程ID
+        mobile: '', // 手机号
+        pageNum: 1, // 当前页码
+        pageSize: 30, // 每页记录数
+        total: 0, // 总记录数
+      },
+
+      // ============= 页面状态 =============
+      loading: false, // 加载状态
+
+      // ============= 数据相关 =============
+      tableData: [], // 表格数据
+
+      // ============= 下拉选项数据 =============
+      companyOptionsParams: {
+        name: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      companyOptionsLoading: false,
+      dealerOptions: [], // 经销商选项
+      companyUserOptionsParams: {
+        name: undefined,
+        companyId: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      companyUserOptionsLoading: false,
+      groupOptions: [], // 群组选项
+      userOptionsParams: {
+        name: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      userOptionsLoading: false,
+      memberOptions: [], // 会员选项
+      trainerOptionsParams: {
+        name: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      trainerOptionsLoading: false,
+      trainerOptions: [], // 训练营选项
+      campPeriodOptionsParams: {
+        name: undefined,
+        campId: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      campPeriodOptionsLoading: false,
+      campPeriodOptions: [], // 营期选项
+      courseOptionsParams: {
+        name: undefined,
+        periodId: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      courseOptionsLoading: false,
+      courseOptions: [] // 课程选项
+    }
+  },
+
+  // ============= 生命周期钩子 =============
+  created() {
+    // 页面创建时初始化数据
+    this.getList() // 加载表格数据
+  },
+
+  // ============= 组件方法 =============
+  methods: {
+    /**
+     * 获取表格数据
+     * 根据查询参数从服务器获取会员统计数据
+     */
+    getList() {
+      this.loading = true // 显示加载中状态
+      // 模拟异步请求
+      let query = {
+        type: this.queryParams.dateType === 'day' ? 1 : 2,
+        startDate: moment(this.queryParams.dateRange[0]).format('YYYY-MM-DD'),
+        endDate: moment(this.queryParams.dateRange[1]).format('YYYY-MM-DD'),
+        companyId: this.queryParams.dealerId,
+        companyUserId: this.queryParams.groupId,
+        userId: this.queryParams.memberId,
+        phone: this.queryParams.mobile,
+        trainCampId: this.queryParams.trainerId,
+        periodId: this.queryParams.campPeriod,
+        videoId: this.queryParams.courseId,
+        pageNum: this.queryParams.pageNum,
+        pageSize: this.queryParams.pageSize
+      }
+      dailyData(query).then(response => {
+        if (response && response.code === 200) {
+          this.tableData = response.data.list
+          this.queryParams.total = response.data.total
+          this.loading = false // 隐藏加载中状态
+        }
+      })
+    },
+
+    // 切换日期类型
+    handleChangeDateType() {
+      this.queryParams.dateRange  = [new Date(), new Date()]
+
+      // 重新请求数据
+      this.getList()
+    },
+
+    /**
+     * 查询按钮点击事件
+     * 根据筛选条件重新查询数据
+     */
+    handleQuery() {
+      this.queryParams.pageNum = 1 // 重置为第一页
+      this.getList() // 重新获取数据
+    },
+
+    /**
+     * 重置查询条件
+     * 清空所有筛选条件并重新加载数据
+     */
+    resetQuery() {
+      // 重置所有查询条件
+      this.queryParams = {
+        dateType: 'day', // 重置日期类型为按天
+        dateRange: [], // 清空日期范围
+        publicId: '', // 清空公众号ID
+        dealerId: '', // 清空经销商ID
+        groupId: '', // 清空群组ID
+        memberId: '', // 清空会员ID
+        trainerId: '', // 清空训练官ID
+        campPeriod: '', // 清空营期
+        courseId: '', // 清空课程ID
+        mobile: '' // 清空手机号
+      }
+      this.getList() // 重新获取数据
+    },
+
+    // 加载经销商选项
+    loadCompanyOptions(query) {
+      this.dealerOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.companyOptionsParams.pageNum = 1
+      this.companyOptionsParams.name = query
+      this.companyOptionsLoading = true;
+      this.getCompanyListLikeName()
+    },
+    getCompanyListLikeName() {
+      getCompanyListLikeName(this.companyOptionsParams).then(response => {
+        this.dealerOptions = [...this.dealerOptions, ...response.data.list]
+        this.companyOptionsParams.hasNextPage = response.data.hasNextPage
+        this.companyOptionsLoading = false;
+      });
+    },
+    loadMoreCompanyOptions() {
+      if (!this.companyOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.companyOptionsParams.pageNum += 1
+      this.getCompanyListLikeName()
+    },
+    handleChangeDealer(val) {
+      this.groupOptions = [];
+      this.companyUserOptionsLoading = true;
+      if (!val) {
+        this.companyUserOptionsParams.companyId = undefined
+        this.getCompanyUserListLikeName()
+        return
+      }
+
+      this.companyUserOptionsParams.companyId = val
+      this.companyUserOptionsParams.pageNum = 1
+      this.getCompanyUserListLikeName()
+    },
+
+    // 群组选项
+    loadCompanyUserOptions(query) {
+      this.groupOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.companyUserOptionsParams.pageNum = 1
+      this.companyUserOptionsParams.name = query
+      this.companyUserOptionsLoading = true;
+      this.getCompanyUserListLikeName()
+    },
+    getCompanyUserListLikeName() {
+      getCompanyUserListLikeName(this.companyUserOptionsParams).then(response => {
+        this.groupOptions = [...this.groupOptions, ...response.data.list]
+        this.companyUserOptionsParams.hasNextPage = response.data.hasNextPage
+        this.companyUserOptionsLoading = false;
+      });
+    },
+    loadMoreCompanyUserOptions() {
+      if (!this.companyUserOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.companyUserOptionsParams.pageNum += 1
+      this.getCompanyUserListLikeName()
+    },
+
+    // 会员选项
+    loadUserOptions(query) {
+      this.memberOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.userOptionsParams.pageNum = 1
+      this.userOptionsParams.name = query
+      this.userOptionsLoading = true;
+      this.getUserListLikeName()
+    },
+    getUserListLikeName() {
+      getUserListLikeName(this.userOptionsParams).then(response => {
+        this.memberOptions = [...this.memberOptions, ...response.data.list]
+        this.userOptionsParams.hasNextPage = response.data.hasNextPage
+        this.userOptionsLoading = false;
+      });
+    },
+    loadMoreUserOptions() {
+      if (!this.userOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.userOptionsParams.pageNum += 1
+      this.getUserListLikeName()
+    },
+
+    // 训练营选项
+    loadTrainerOptions(query) {
+      this.trainerOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.trainerOptionsParams.pageNum = 1
+      this.trainerOptionsParams.name = query
+      this.trainerOptionsLoading = true;
+      this.getTrainerListLikeName()
+    },
+    getTrainerListLikeName() {
+      getCampListLikeName(this.trainerOptionsParams).then(response => {
+        this.trainerOptions = [...this.trainerOptions, ...response.data.list]
+        this.trainerOptionsParams.hasNextPage = response.data.hasNextPage
+        this.trainerOptionsLoading = false;
+      });
+    },
+    loadMoreTrainerOptions() {
+      if (!this.trainerOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.trainerOptionsParams.pageNum += 1
+      this.getTrainerListLikeName()
+    },
+    changeTrainer(val) {
+      this.campPeriodOptions = [];
+      this.campPeriodOptionsLoading = true;
+      if (!val) {
+        this.campPeriodOptionsParams.companyId = undefined
+        this.getPeriodListLikeName()
+        return
+      }
+
+      this.campPeriodOptionsParams.companyId = val
+      this.campPeriodOptionsParams.pageNum = 1
+      this.getPeriodListLikeName()
+    },
+
+    // 营期选项
+    loadPeriodOptions(query) {
+      this.campPeriodOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.campPeriodOptionsParams.pageNum = 1
+      this.campPeriodOptionsParams.name = query
+      this.campPeriodOptionsLoading = true;
+      this.getPeriodListLikeName()
+    },
+    getPeriodListLikeName() {
+      getPeriodListLikeName(this.campPeriodOptionsParams).then(response => {
+        this.campPeriodOptions = [...this.campPeriodOptions, ...response.data.list]
+        this.campPeriodOptionsParams.hasNextPage = response.data.hasNextPage
+        this.campPeriodOptionsLoading = false;
+      });
+    },
+    loadMorePeriodOptions() {
+      if (!this.campPeriodOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.campPeriodOptionsParams.pageNum += 1
+      this.getPeriodListLikeName()
+    },
+    changeCampPeriod(val) {
+      this.courseOptions = []
+      this.courseOptionsLoading = true;
+      if (!val) {
+        this.courseOptionsParams.periodId = undefined
+        this.getCourseListLikeName()
+        return
+      }
+
+      this.courseOptionsParams.periodId = val
+      this.courseOptionsParams.pageNum = 1
+      this.getCourseListLikeName()
+    },
+
+    // 课程选项
+    loadCourseOptions(query) {
+      this.courseOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.courseOptionsParams.pageNum = 1
+      this.courseOptionsParams.name = query
+      this.courseOptionsLoading = true;
+      this.getCourseListLikeName()
+    },
+    getCourseListLikeName() {
+      getVideoListLikeName(this.courseOptionsParams).then(response => {
+        this.courseOptions = [...this.courseOptions, ...response.data.list]
+        this.courseOptionsParams.hasNextPage = response.data.hasNextPage
+        this.courseOptionsLoading = false;
+      });
+    },
+    loadMoreCourseOptions() {
+      if (!this.courseOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.courseOptionsParams.pageNum += 1
+      this.getCourseListLikeName()
+    },
+  }
+}
+</script>
+
+<style scoped>
+.member-statistics-page {
+  padding: 10px;
+}
+/* 会员统计页面容器样式 */
+.member-statistics-container {
+  padding: 10px;
+  background-color: #fff;
+}
+
+/* 筛选区域样式 */
+.filter-container {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  background-color: #fff;
+  padding: 15px;
+  border-radius: 4px;
+}
+
+.filter-item {
+  margin-right: 15px;
+  margin-bottom: 10px;
+}
+
+/* 表格容器样式 */
+.table-container {
+  background-color: #FFF;
+  height: 72vh;
+}
+
+/* 表格深度样式 */
+::v-deep .el-table th {
+  color: #606266;
+  font-weight: bold;
+}
+
+::v-deep .el-table--border th, ::v-deep .el-table--border td {
+  border-right: 1px solid #EBEEF5;
+}
+
+::v-deep .el-table {
+  border: 1px solid #EBEEF5;
+  border-bottom: none;
+}
+
+::v-deep .el-input__inner {
+  border-radius: 4px;
+}
+
+::v-deep .el-button {
+  border-radius: 4px;
+}
+</style>

Some files were not shown because too many files changed in this diff