Bladeren bron

Merge remote-tracking branch 'origin/master'

yjwang 1 dag geleden
bovenliggende
commit
973f225a85
55 gewijzigde bestanden met toevoegingen van 6402 en 836 verwijderingen
  1. 38 0
      .env.prod-fby
  2. 2 2
      .env.prod-hcl
  3. 1 0
      package.json
  4. 53 0
      src/api/course/fsCourseProduct.js
  5. 70 0
      src/api/course/fsCourseProductOrder.js
  6. 8 0
      src/api/course/userCourseComplaintRecord.js
  7. 1 1
      src/api/course/userCoursePublic.js
  8. 71 0
      src/api/course/userWatchCourseStatistics.js
  9. 53 0
      src/api/course/userWatchStatistics.js
  10. 46 0
      src/api/his/packageSolarTerm.js
  11. 17 0
      src/api/his/redPackage.js
  12. 16 0
      src/api/his/storeOrder.js
  13. 43 0
      src/api/his/user.js
  14. 53 0
      src/api/his/userOnlineState.js
  15. BIN
      src/assets/logo/fby_logo.png
  16. 1 0
      src/utils/cos.js
  17. 1 1
      src/views/company/company/index.vue
  18. 4 4
      src/views/company/companyRecharge/index.vue
  19. 249 272
      src/views/components/course/userCourseCatalogDetails.vue
  20. 1 0
      src/views/components/his/integralGoodsDetails.vue
  21. 8 2
      src/views/components/his/integralOrderDetails.vue
  22. 9 3
      src/views/components/his/storeOrderDetails.vue
  23. 165 0
      src/views/course/fsCourseProduct/CourseProduct.vue
  24. 316 0
      src/views/course/fsCourseProduct/index.vue
  25. 523 0
      src/views/course/fsCourseProductOrder/index.vue
  26. 0 3
      src/views/course/userCourse/index.vue
  27. 3 0
      src/views/course/userCourse/public.vue
  28. 51 1
      src/views/course/userCourseComplaintRecord/index.vue
  29. 3 4
      src/views/course/userCoursePeriod/batchRedPacket.vue
  30. 27 11
      src/views/course/userCoursePeriod/index.vue
  31. 6 9
      src/views/course/userCoursePeriod/redPacket.vue
  32. 5 88
      src/views/course/userTalent/index.vue
  33. 390 0
      src/views/course/userWatchCourseStatistics/index.vue
  34. 394 0
      src/views/course/userWatchCourseTotalStatistics/index.vue
  35. 286 0
      src/views/course/userWatchStatistics/index.vue
  36. 767 90
      src/views/course/videoResource/index.vue
  37. 287 0
      src/views/his/bill/redPackage/index.vue
  38. 43 0
      src/views/his/company/index.vue
  39. 5 0
      src/views/his/integralGoods/index.vue
  40. 1 0
      src/views/his/integralOrder/index.vue
  41. 42 2
      src/views/his/package/index.vue
  42. 336 0
      src/views/his/packageSolarTerm/index.vue
  43. 91 21
      src/views/his/storeOrder/order1.vue
  44. 44 2
      src/views/his/storeProduct/index.vue
  45. 81 143
      src/views/his/user/index.vue
  46. 794 0
      src/views/his/user/indexProject.vue
  47. 400 0
      src/views/his/userOnlineState/index.vue
  48. 63 38
      src/views/statistics/section/inline.vue
  49. 60 103
      src/views/system/config/config.vue
  50. 7 0
      src/views/system/config/config2.vue
  51. 221 0
      src/views/system/config/integralConfig.vue
  52. 13 0
      src/views/system/role/index.vue
  53. 182 0
      src/views/user/blacklist/index.vue
  54. 35 33
      src/views/user/darkRoom/index.vue
  55. 16 3
      src/views/user/transfer/index.vue

+ 38 - 0
.env.prod-fby

@@ -0,0 +1,38 @@
+# 页面标题
+VUE_APP_TITLE =互联网医院管理系统
+# 公司名称
+COMPANY_NAME =福本源
+# ICP备案号
+ICP_RECORD =蜀ICP备2023036719号
+# ICP网站访问地址
+ICP_URL =https://beian.miit.gov.cn
+# 网站LOG
+VUE_APP_LOG_URL =@/assets/logo/fby_logo.png
+# 存储桶配置
+VUE_APP_OBS_ACCESS_KEY_ID = K2UTJGIN7UTZJR2XMXYG
+# 存储桶配置
+VUE_APP_OBS_SECRET_ACCESS_KEY = sbyeNJLbcYmH6copxeFP9pAoksM4NIT9Zw4x0SRX
+# 存储桶配置
+VUE_APP_OBS_SERVER = https://obs.cn-north-4.myhuaweicloud.com
+# 存储桶配置
+VUE_APP_OBS_BUCKET = fby-hw079058881
+# 存储桶配置
+VUE_APP_COS_BUCKET = fby-1323137866
+# 存储桶配置
+VUE_APP_COS_REGION = ap-chongqing
+# 线路一地址
+VUE_APP_VIDEO_LINE_1 = https://fbytcpv.ylrzcloud.com
+# 线路二地址
+VUE_APP_VIDEO_LINE_2 = https://fbyobs.ylrztop.com
+# 生产环境配置
+ENV = 'production'
+
+# FS管理系统/开发环境
+VUE_APP_BASE_API = '/prod-api'
+
+#默认 1、会员 2、企微
+VUE_APP_COURSE_DEFAULT = 1
+
+# 路由懒加载
+VUE_CLI_BABEL_TRANSPILE_MODULES = true
+

+ 2 - 2
.env.prod-hcl

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

+ 1 - 0
package.json

@@ -32,6 +32,7 @@
     "build:prod-cqxzt": "vue-cli-service build --mode prod-cqxzt",
     "build:prod-bjyjb": "vue-cli-service build --mode prod-bjyjb",
     "build:prod-bjczwh": "vue-cli-service build --mode prod-bjczwh",
+    "build:prod-fby": "vue-cli-service build --mode prod-fby",
     "preview": "node build/index.js --preview",
     "lint": "eslint --ext .js,.vue src"
   },

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

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询拍单商品列表
+export function listFsCourseProduct(query) {
+  return request({
+    url: '/course/fsCourseProduct/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询拍单商品详细
+export function getFsCourseProduct(id) {
+  return request({
+    url: '/course/fsCourseProduct/' + id,
+    method: 'get'
+  })
+}
+
+// 新增拍单商品
+export function addFsCourseProduct(data) {
+  return request({
+    url: '/course/fsCourseProduct',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改拍单商品
+export function updateFsCourseProduct(data) {
+  return request({
+    url: '/course/fsCourseProduct',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除拍单商品
+export function delFsCourseProduct(id) {
+  return request({
+    url: '/course/fsCourseProduct/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出拍单商品
+export function exportFsCourseProduct(query) {
+  return request({
+    url: '/course/fsCourseProduct/export',
+    method: 'get',
+    params: query
+  })
+}

+ 70 - 0
src/api/course/fsCourseProductOrder.js

@@ -0,0 +1,70 @@
+import request from '@/utils/request'
+
+// 查询拍单商品订单列表
+export function listFsCourseProductOrder(query) {
+  return request({
+    url: '/course/fsCourseProductOrder/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询拍单商品订单详细
+export function getFsCourseProductOrder(courseOrderId) {
+  return request({
+    url: '/course/fsCourseProductOrder/' + courseOrderId,
+    method: 'get'
+  })
+}
+
+// 新增拍单商品订单
+export function addFsCourseProductOrder(data) {
+  return request({
+    url: '/course/fsCourseProductOrder',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改拍单商品订单
+export function updateFsCourseProductOrder(data) {
+  return request({
+    url: '/course/fsCourseProductOrder',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除拍单商品订单
+export function delFsCourseProductOrder(courseOrderId) {
+  return request({
+    url: '/course/fsCourseProductOrder/' + courseOrderId,
+    method: 'delete'
+  })
+}
+
+// 导出拍单商品订单
+export function exportFsCourseProductOrder(query) {
+  return request({
+    url: '/course/fsCourseProductOrder/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 退款拍商品订单
+export function refund(data) {
+  return request({
+    url: '/course/fsCourseProductOrder/refund',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询收货人电话
+export function getOrderUserPhone(courseOrderId) {
+  return request({
+    url: '/course/fsCourseProductOrder/queryPhone/' + courseOrderId,
+    method: 'get'
+  })
+}

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

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

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

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

+ 71 - 0
src/api/course/userWatchCourseStatistics.js

@@ -0,0 +1,71 @@
+import request from '@/utils/request'
+
+// 查询会员看课统计-按课程统计列表
+export function listUserWatchCourseStatistics(query) {
+  return request({
+    url: '/course/userWatchCourseStatistics/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询会员看课统计-按课程统计详细
+export function getUserWatchCourseStatistics(id) {
+  return request({
+    url: '/course/userWatchCourseStatistics/' + id,
+    method: 'get'
+  })
+}
+
+// 新增会员看课统计-按课程统计
+export function addUserWatchCourseStatistics(data) {
+  return request({
+    url: '/course/userWatchCourseStatistics',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改会员看课统计-按课程统计
+export function updateUserWatchCourseStatistics(data) {
+  return request({
+    url: '/course/userWatchCourseStatistics',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除会员看课统计-按课程统计
+export function delUserWatchCourseStatistics(id) {
+  return request({
+    url: '/course/userWatchCourseStatistics/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出会员看课统计-按课程统计
+export function exportUserWatchCourseStatistics(query) {
+  return request({
+    url: '/course/userWatchCourseStatistics/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询会员看课统计-按课程统计列表
+export function listUserWatchCourseStatisticsTotal(query) {
+  return request({
+    url: '/course/userWatchCourseStatistics/listTotal',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出会员看课统计-按课程统计汇总
+export function exportUserWatchCourseStatisticsTotal(query) {
+  return request({
+    url: '/course/userWatchCourseStatistics/exportTotal',
+    method: 'get',
+    params: query
+  })
+}

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

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询会员看课统计-按营期统计列表
+export function listUserWatchStatistics(query) {
+  return request({
+    url: '/course/userWatchStatistics/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询会员看课统计-按营期统计详细
+export function getUserWatchStatistics(id) {
+  return request({
+    url: '/course/userWatchStatistics/' + id,
+    method: 'get'
+  })
+}
+
+// 新增会员看课统计-按营期统计
+export function addUserWatchStatistics(data) {
+  return request({
+    url: '/course/userWatchStatistics',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改会员看课统计-按营期统计
+export function updateUserWatchStatistics(data) {
+  return request({
+    url: '/course/userWatchStatistics',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除会员看课统计-按营期统计
+export function delUserWatchStatistics(id) {
+  return request({
+    url: '/course/userWatchStatistics/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出会员看课统计-按营期统计
+export function exportUserWatchStatistics(query) {
+  return request({
+    url: '/course/userWatchStatistics/export',
+    method: 'get',
+    params: query
+  })
+}

+ 46 - 0
src/api/his/packageSolarTerm.js

@@ -0,0 +1,46 @@
+import request from '@/utils/request'
+
+export function listPackageSolarTerm(query) {
+  return request({
+    url: '/his/packageSolarTerm/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function getPackageSolarTerm(solarTermId) {
+  return request({
+    url: '/his/packageSolarTerm/' + solarTermId,
+    method: 'get'
+  })
+}
+
+export function addPackageSolarTerm(data) {
+  return request({
+    url: '/his/packageSolarTerm',
+    method: 'post',
+    data: data
+  })
+}
+
+export function updatePackageSolarTerm(data) {
+  return request({
+    url: '/his/packageSolarTerm',
+    method: 'put',
+    data: data
+  })
+}
+
+export function delPackageSolarTerm(solarTermIds) {
+  return request({
+    url: '/his/packageSolarTerm/' + solarTermIds,
+    method: 'delete'
+  })
+}
+
+export function getOptions() {
+  return request({
+    url: '/his/packageSolarTerm/getOptions',
+    method: 'get'
+  })
+}

+ 17 - 0
src/api/his/redPackage.js

@@ -0,0 +1,17 @@
+import request from '@/utils/request'
+
+
+export function redPackageTotal(query) {
+  return request({
+    url: '/company/redPackage/list',
+    method: 'get',
+    params: query
+  })
+}
+export function exportRedPackageByTime(query) {
+  return request({
+    url: '/company/redPackage/export',
+    method: 'get',
+    params: query
+  })
+}

+ 16 - 0
src/api/his/storeOrder.js

@@ -15,6 +15,13 @@ export function importTemplate() {
     method: 'get'
   })
 }
+// 下载导入模板
+export function importUpdateOrderTemplate() {
+  return request({
+    url: '/his/storeOrder/importUpdateOrderTemplate',
+    method: 'get'
+  })
+}
 // 查询订单详细
 export function getOrder(orderId) {
   return request({
@@ -149,6 +156,15 @@ export function updateOrder(data) {
     data: data
   })
 }
+// 修改订单
+export function updateStoreOrder(data) {
+  return request({
+    url: '/his/storeOrder/updateStoreOrder',
+    method: 'put',
+    data: data
+  })
+}
+
 export function updateDelivery(data) {
   return request({
     url: '/his/storeOrder/updateDelivery',

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

@@ -90,3 +90,46 @@ export function enabledUsers(data) {
     data: data
   })
 }
+
+// 获取黑名单用户列表
+export function blacklist(query) {
+  return request({
+    url: '/his/user/blacklist',
+    method: 'get',
+    params: query
+  })
+}
+
+// 批量解禁
+export function enabledBlackUsers(data) {
+  return request({
+    url: '/his/user/enabledBlackUsers',
+    method: 'post',
+    data: data
+  })
+}
+
+// 添加积分
+export function addPoint(data) {
+  return request({
+    url: '/his/user/addPoints',
+    method: 'post',
+    data: data
+  })
+}
+
+export function listUserByProject(query) {
+  return request({
+    url: '/his/user/listProject',
+    method: 'get',
+    params: query
+  })
+}
+
+// 删除用户
+export function delUserCompanyUser(id) {
+  return request({
+    url: '/his/user/delete/' + id,
+    method: 'delete'
+  })
+}

+ 53 - 0
src/api/his/userOnlineState.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询用户上线情况列表
+export function listUserOnlineState(query) {
+  return request({
+    url: '/store/userOnlineState/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询用户上线情况详细
+export function getUserOnlineState(userId) {
+  return request({
+    url: '/store/userOnlineState/' + userId,
+    method: 'get'
+  })
+}
+
+// 新增用户上线情况
+export function addUserOnlineState(data) {
+  return request({
+    url: '/store/userOnlineState',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改用户上线情况
+export function updateUserOnlineState(data) {
+  return request({
+    url: '/store/userOnlineState',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除用户上线情况
+export function delUserOnlineState(userId) {
+  return request({
+    url: '/store/userOnlineState/' + userId,
+    method: 'delete'
+  })
+}
+
+// 导出用户上线情况
+export function exportUserOnlineState(query) {
+  return request({
+    url: '/store/userOnlineState/export',
+    method: 'get',
+    params: query
+  })
+}

BIN
src/assets/logo/fby_logo.png


+ 1 - 0
src/utils/cos.js

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

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

@@ -550,7 +550,7 @@ export default {
 
     handleResetPwd(row) {
       const companyIds = row.companyId || this.ids;
-      this.$confirm('是否确认重复密码为123456?', "警告", {
+      this.$confirm('是否确认重置密码为cq654321!!', "警告", {
           confirmButtonText: "确定",
           cancelButtonText: "取消",
           type: "warning"

+ 4 - 4
src/views/company/companyRecharge/index.vue

@@ -97,17 +97,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 +197,7 @@ export default {
   created() {
      getCompanyList().then(response => {
       this.companys = response.data;
-       
+
     });
     this.getDicts("sys_comapny_recharge_pay_type").then((response) => {
       this.payTypeOptions = response.data;

+ 249 - 272
src/views/components/course/userCourseCatalogDetails.vue

@@ -1,17 +1,12 @@
 <template>
   <div class="app-container">
     <div style="padding-bottom: 20px">
-      <span v-if="courseName!=null">{{ courseName }}</span>
+      <span v-if="courseName != null">{{ courseName }}</span>
     </div>
     <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
       <el-form-item label="小节名称" prop="title">
-        <el-input
-          v-model="queryParams.title"
-          placeholder="请输入小节名称"
-          clearable
-          size="small"
-          @keyup.enter.native="handleQuery"
-        />
+        <el-input v-model="queryParams.title" 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>
@@ -20,63 +15,33 @@
     </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:userCourseVideo:add']"
-        >新增目录</el-button>
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
+          v-hasPermi="['course:userCourseVideo:add']">新增目录</el-button>
       </el-col>
       <el-col :span="1.5">
-        <el-button
-          type="primary"
-          plain
-          :disabled="!ids || ids.length <= 0"
-          size="mini"
-          @click="openUpdates"
-          v-hasPermi="['course:userCourseVideo:updateTime']"
-        >修改时间</el-button>
+        <el-button type="primary" plain :disabled="!ids || ids.length <= 0" size="mini" @click="openUpdates"
+          v-hasPermi="['course:userCourseVideo:updateTime']">修改时间</el-button>
       </el-col>
       <el-col :span="1.5">
-        <el-button
-          type="primary"
-          plain
-          size="mini"
-          @click="openAdds"
-          v-hasPermi="['course:userCourseVideo:batchAdd']"
-        >批量添加</el-button>
+        <el-button type="primary" plain size="mini" @click="openAdds"
+          v-hasPermi="['course:userCourseVideo:batchAdd']">批量添加</el-button>
       </el-col>
       <el-col :span="1.5">
-        <el-button
-          type="primary"
-          plain
-          size="mini"
-          v-if="isPrivate === 1"
-          @click="updateRedPageckeOpen"
-          v-hasPermi="['course:userCourseVideo:updateRed']"
-        >修改红包</el-button>
+        <el-button type="primary" plain size="mini" v-if="isPrivate === 1" @click="updateRedPageckeOpen"
+          v-hasPermi="['course:userCourseVideo:updateRed']">修改红包</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:userCourseVideo:remove']"
-        >删除</el-button>
+        <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"
+          v-hasPermi="['course:userCourseVideo:remove']">删除</el-button>
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
     <el-table border v-loading="loading" :data="userCourseVideoList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="视频ID" align="center" prop="videoId" />
-<!--      <el-table-column label="轮次" align="center" prop="round" />-->
+      <!--      <el-table-column label="轮次" align="center" prop="round" />-->
       <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 label="视频文件名称" align="center" show-overflow-tooltip prop="fileName">
       </el-table-column>
       <el-table-column label="视频时长" align="center" prop="duration">
         <template slot-scope="{ row }">
@@ -85,13 +50,13 @@
       </el-table-column>
       <el-table-column label="看课开始时间" align="center" prop="duration">
         <template slot-scope="{ row }">
-          <el-tag v-if="row.viewStartTime">{{row.viewStartTime}}</el-tag>
+          <el-tag v-if="row.viewStartTime">{{ row.viewStartTime }}</el-tag>
           <el-tag type="danger" v-if="!row.viewStartTime">无</el-tag>
         </template>
       </el-table-column>
       <el-table-column label="看课结束时间" align="center" prop="duration">
         <template slot-scope="{ row }">
-          <el-tag v-if="row.viewEndTime">{{row.viewEndTime}}</el-tag>
+          <el-tag v-if="row.viewEndTime">{{ row.viewEndTime }}</el-tag>
           <el-tag type="danger" v-if="!row.viewEndTime">无</el-tag>
         </template>
       </el-table-column>
@@ -101,56 +66,36 @@
           <el-tag type="danger" v-if="!row.lastJoinTime">无</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="红包金额" align="center" prop="redPacketMoney" v-if="isPrivate === 1"/>
+      <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">
         <template slot-scope="scope">
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-edit"
-            @click="handleUpdate(scope.row)"
-            v-hasPermi="['course:userCourseVideo:edit']"
-          >修改</el-button>
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-edit"
-            @click="handleComment(scope.row)"
-            v-hasPermi="['course:courseWatchComment:list']"
-          >查看评论</el-button>
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-delete"
-            @click="handleDelete(scope.row)"
-            v-hasPermi="['course:userCourseVideo:remove']"
-          >删除</el-button>
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
+            v-hasPermi="['course:userCourseVideo:edit']">修改</el-button>
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleComment(scope.row)"
+            v-hasPermi="['course:courseWatchComment:list']">查看评论</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+            v-hasPermi="['course:userCourseVideo: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 >
+    <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="110px" v-loading="uploadLoading">
         <el-form-item label="视频标题" prop="title">
-          <el-input v-model="form.title"  placeholder="请输入内容" />
+          <el-input v-model="form.title" placeholder="请输入内容" />
         </el-form-item>
         <el-form-item label="视频描述" prop="description">
-          <el-input v-model="form.description" type="textarea" :rows="2"  placeholder="请输入内容" />
+          <el-input v-model="form.description" type="textarea" :rows="2" placeholder="请输入内容" />
         </el-form-item>
-<!--        <el-form-item label="轮次" prop="round">-->
-<!--          <el-input v-model="form.round"  placeholder="请输入内容" />-->
-<!--        </el-form-item>-->
+        <!--        <el-form-item label="轮次" prop="round">-->
+        <!--          <el-input v-model="form.round"  placeholder="请输入内容" />-->
+        <!--        </el-form-item>-->
         <el-form-item label="课程排序" prop="courseSort">
-          <el-input-number v-model="form.courseSort" :min="1" ></el-input-number>
+          <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-->
@@ -174,46 +119,28 @@
         <!--        </el-form-item>-->
 
         <el-form-item label="视频缩略图" prop="thumbnail">
-          <el-upload
-            v-model="form.thumbnail"
-            class="avatar-uploader"
-            :action="uploadUrl"
-            :show-file-list="false"
-            :on-success="handleAvatarSuccess"
-            :before-upload="beforeAvatarUpload">
+          <el-upload v-model="form.thumbnail" class="avatar-uploader" :action="uploadUrl" :show-file-list="false"
+            :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
             <img v-if="form.thumbnail" :src="form.thumbnail" class="avatar" width="300px">
             <i v-else class="el-icon-plus avatar-uploader-icon"></i>
           </el-upload>
         </el-form-item>
-        <video-upload
-          :type = "1"
-          :isPrivate = "isPrivate"
-          :fileKey.sync = "form.fileKey"
-          :fileSize.sync = "form.fileSize"
-          :videoUrl.sync="videoUrl"
-          :fileName.sync="form.fileName"
-          :line_1.sync="form.lineOne"
-          :line_2.sync="form.lineTwo"
-          :line_3.sync="form.lineThree"
-          :thumbnail.sync="form.thumbnail"
-          :uploadType.sync="form.uploadType"
-          :isTranscode.sync="form.isTranscode"
-          :transcodeFileKey.sync="form.transcodeFileKey"
-          @video-duration="handleVideoDuration"
-          @change="handleVideoChange"
-          @selectProjects="handleSelectProjects"
-          ref="videoUpload"
-          append-to-body
-        />
-
+        <video-upload :type="1" :isPrivate="isPrivate" :fileKey.sync="form.fileKey" :fileSize.sync="form.fileSize"
+          :videoUrl.sync="videoUrl" :fileName.sync="form.fileName" :line_1.sync="form.lineOne"
+          :line_2.sync="form.lineTwo" :line_3.sync="form.lineThree" :thumbnail.sync="form.thumbnail"
+          :uploadType.sync="form.uploadType" :isTranscode.sync="form.isTranscode"
+          :transcodeFileKey.sync="form.transcodeFileKey" @video-duration="handleVideoDuration"
+          @change="handleVideoChange" @selectProjects="handleSelectProjects" ref="videoUpload" append-to-body />
+        
         <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">
+          <el-table border width="100%" style="margin-top:5px;" :data="form.questionBankList">
 
             <el-table-column label="问题" align="center" prop="title">
               <template slot-scope="scope">
                 <el-tooltip class="item" effect="dark" :content="scope.row.title" placement="top">
-                  <div style="display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; text-overflow: ellipsis;">
+                  <div
+                    style="display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; text-overflow: ellipsis;">
                     <span>{{ scope.row.title }}</span>
                   </div>
                 </el-tooltip>
@@ -221,50 +148,68 @@
             </el-table-column>
             <el-table-column label="类别" align="center" prop="type">
               <template slot-scope="scope">
-                <dict-tag :options="typeOptions" :value="scope.row.type"/>
+                <dict-tag :options="typeOptions" :value="scope.row.type" />
               </template>
             </el-table-column>
             <el-table-column label="答案" align="center" prop="answer" />
             <el-table-column label="操作" align="center" width="100px" fixed="right">
               <template slot-scope="scope">
-                <el-button
-                  size="mini"
-                  type="text"
-                  icon="el-icon-delete"
-                  @click="handleQuestionBankDelete(scope.row)"
-                >删除</el-button>
+                <el-button size="mini" type="text" icon="el-icon-delete"
+                  @click="handleQuestionBankDelete(scope.row)">删除</el-button>
               </template>
             </el-table-column>
           </el-table>
-        </el-form-item >
+        </el-form-item>
         <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-input-number v-model="form.redPacketMoney" :min="0.1" :max="200" :step="0.1"></el-input-number>
+        </el-form-item>
+        <el-form-item label="是否关联商品">
+          <el-radio v-model="form.isProduct" :label=0>否</el-radio>
+          <el-radio v-model="form.isProduct" :label=1>是</el-radio>
+        </el-form-item>
+        <el-form-item label="商品选择" v-if="form.isProduct === 1">
+          <el-button size="small" type="primary" @click="chooseCourseProduct">选取商品</el-button>
+          <el-table border width="100%" style="margin-top:5px;" :data="form.courseProducts">
+            <el-table-column label="商品名称" align="center" prop="productName" />
+            <el-table-column label="产品条码" align="center" prop="barCode" />
+            <el-table-column label="商品价格" align="center" prop="productPrice" />
+            <el-table-column label="库存" align="center" prop="stock" />
+            <el-table-column label="操作" align="center" width="100px" fixed="right">
+              <template slot-scope="scope">
+                <el-button size="mini" type="text" icon="el-icon-delete"
+                  @click="handleCourseProductDelete(scope.row)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
         </el-form-item>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item v-if="form.isProduct === 1" label="商品售卖时间" prop="listingStartTime">
+              <el-input-number v-model="form.listingStartTime" :min="0" label="商品售卖时间"></el-input-number>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item v-if="form.isProduct === 1"  label="结束售卖时间" prop="listingStartTime">
+              <el-input-number v-model="form.listingEndTime" :min="0" label="结束售卖时间"></el-input-number>
+            </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>
     </el-dialog>
-    <el-dialog :title="title" :visible.sync="updateBatchData.open" width="1000px" append-to-body >
+    <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 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 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>
@@ -274,39 +219,30 @@
         <el-button @click="updateBatchData.open = false">取 消</el-button>
       </div>
     </el-dialog>
-    <el-dialog :title="questionBank.title" :visible.sync="questionBank.open" width="1000px" append-to-body >
-      <question-bank ref="questionBank" @questionBankResult="questionBankResult" ></question-bank>
+    <el-dialog :title="questionBank.title" :visible.sync="questionBank.open" width="1000px" append-to-body>
+      <question-bank ref="questionBank" @questionBankResult="questionBankResult"></question-bank>
+    </el-dialog>
+    <el-dialog :title="courseProduct.title" :visible.sync="courseProduct.open" width="1000px" append-to-body>
+      <course-product ref="courseProduct" @courseProductResult="courseProductResult"></course-product>
     </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-input v-model="addBatchData.queryParams.resourceName" placeholder="请输入素材名称" clearable size="small"
+            @keyup.enter.native="resourceList" />
         </el-form-item>
         <el-form-item label="类型">
-          <el-select v-model="addBatchData.queryParams.typeId" @change="changeCateType" placeholder="请选择素材类型" clearable size="small">
-            <el-option
-              v-for="item in addBatchData.typeOptions"
-              :key="item.dictValue"
-              :label="item.dictLabel"
-              :value="item.dictValue"
-            />
+          <el-select v-model="addBatchData.queryParams.typeId" @change="changeCateType" placeholder="请选择素材类型" clearable
+            size="small">
+            <el-option v-for="item in addBatchData.typeOptions" :key="item.dictValue" :label="item.dictLabel"
+              :value="item.dictValue" />
           </el-select>
         </el-form-item>
         <el-form-item label="子类型">
           <el-select v-model="addBatchData.queryParams.typeSubId" placeholder="请选择素材子类型" clearable size="small">
-            <el-option
-              v-for="item in addBatchData.typeSubOptions"
-              :key="item.dictValue"
-              :label="item.dictLabel"
-              :value="item.dictValue"
-            />
+            <el-option v-for="item in addBatchData.typeSubOptions" :key="item.dictValue" :label="item.dictLabel"
+              :value="item.dictValue" />
           </el-select>
         </el-form-item>
         <el-form-item>
@@ -315,18 +251,15 @@
       </el-form>
 
       <!-- 视频列表 -->
-      <el-table v-loading="addBatchData.loading" :data="addBatchData.list" @selection-change="handVideoleSelectionChange" height="400px">
+      <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" prop="sort" />
         <el-table-column label="缩略图" align="center">
           <template slot-scope="scope">
-            <el-popover
-              placement="right"
-              title=""
-              trigger="hover"
-            >
+            <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>
@@ -340,13 +273,9 @@
       </el-table>
 
       <!-- 分页 -->
-      <pagination
-        v-show="addBatchData.total>0"
-        :total="addBatchData.total"
-        :page.sync="addBatchData.queryParams.pageNum"
-        :limit.sync="addBatchData.queryParams.pageSize"
-        @pagination="resourceList"
-      />
+      <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>
@@ -355,7 +284,7 @@
     <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 label="视频文件名称" align="center" show-overflow-tooltip prop="fileName">
         </el-table-column>
         <el-table-column label="视频时长" align="center" prop="duration">
           <template slot-scope="{ row }">
@@ -374,11 +303,9 @@
         <el-button type="primary" @click="batchRedSave">确 定</el-button>
       </div>
     </el-dialog>
-    <el-dialog :title="commentDialog.title" :visible.sync="commentDialog.open" width="1000px" append-to-body :close-on-click-modal="false">
-      <course-watch-comment
-        ref="courseWatchComment"
-        :courseId="commentDialog.courseId"
-        :videoId="commentDialog.videoId"
+    <el-dialog :title="commentDialog.title" :visible.sync="commentDialog.open" width="1000px" append-to-body
+      :close-on-click-modal="false">
+      <course-watch-comment ref="courseWatchComment" :courseId="commentDialog.courseId" :videoId="commentDialog.videoId"
         v-if="commentDialog.open">
       </course-watch-comment>
     </el-dialog>
@@ -399,6 +326,7 @@ import {
   updateUserCourseVideo
 } from "@/api/course/userCourseVideo";
 import QuestionBank from "@/views/course/courseQuestionBank/QuestionBank.vue";
+import CourseProduct from "@/views/course/fsCourseProduct/CourseProduct.vue";
 import VideoUpload from "@/components/VideoUpload/index.vue";
 import { listVideoResource } from '@/api/course/videoResource';
 import { getByIds } from '@/api/course/courseQuestionBank'
@@ -407,38 +335,43 @@ import { getCateListByPid, getCatePidList } from '@/api/course/userCourseCategor
 
 export default {
   name: "userCourseCatalog",
-  components: {VideoUpload,QuestionBank,CourseWatchComment},
+  components: { VideoUpload, QuestionBank, CourseWatchComment, CourseProduct,},
   data() {
     return {
       duration: null,
-      packageList:[],
+      packageList: [],
       //课题
-      package:{
-        title:'',
-        open:false,
+      package: {
+        title: '',
+        open: false,
       },
       //课题
-      questionBank:{
-        title:'',
-        open:false,
+      questionBank: {
+        title: '',
+        open: false,
+      },
+      //拍商品
+      courseProduct: {
+        title: '',
+        open: false,
       },
-      isPrivate:null,
+      isPrivate: null,
       videoUrl: "",
       uploadTypeOptions: [
         { dictLabel: "线路一", dictValue: 2 },
 
         { dictLabel: "线路二", dictValue: 3 },
       ],
-      uploadLoading:false,
-      courseId:null,
-      videoName:'',
+      uploadLoading: false,
+      courseId: null,
+      videoName: '',
       title: "",
       // 是否显示弹出层
       open: false,
-      uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS",
+      uploadUrl: process.env.VUE_APP_BASE_API + "/common/uploadOSS",
       baseUrl: process.env.VUE_APP_BASE_API,
-      typeOptions:[],
-      files:[],
+      typeOptions: [],
+      files: [],
       fileList: [],
       // 上传成功后的地址
       videoURL: '',
@@ -446,33 +379,33 @@ export default {
       progress: 0,
       // 上传视频获取成功后拿到的fileID【备用】
       fileId: '',
-      courseName:null,
-      userCourseVideoList:[],
+      courseName: null,
+      userCourseVideoList: [],
       total: 0,
-      redData:{
+      redData: {
         queryParams: {
           pageNum: 1,
           pageSize: 99999,
-          courseId:null,
+          courseId: null,
         },
         list: [],
         open: false,
-        loading:true,
-        form:{
+        loading: true,
+        form: {
         }
       },
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        courseId:null,
-        title:null
+        courseId: null,
+        title: null
       },
       addBatchData: {
         open: false,
         loading: true,
         form: {},
         select: [],
-        total:0,
+        total: 0,
         queryParams: {
           pageNum: 1,
           pageSize: 10,
@@ -499,7 +432,7 @@ export default {
       form: {},
       updateBatchData: {
         open: false,
-        form:{}
+        form: {}
       },
       // 表单校验
       rules: {
@@ -528,8 +461,8 @@ export default {
   methods: {
     getPickerOptions() {
       const durationInMinutes = Math.floor(this.form.duration / 60); // 将秒转换为分钟
-      const endHour  = Math.floor(durationInMinutes / 60); // 起始小时
-      const endMinute  = durationInMinutes % 60; // 起始分钟
+      const endHour = Math.floor(durationInMinutes / 60); // 起始小时
+      const endMinute = durationInMinutes % 60; // 起始分钟
       return {
         start: "00:00", // 固定开始时间
         step: "00:01", // 时间间隔
@@ -538,35 +471,43 @@ export default {
           .padStart(2, "0")}`, // 动态结束时间
       };
     },
-    handlePackageDelete(row){
+    handlePackageDelete(row) {
       this.packageList.splice(this.packageList.findIndex(item => item.packageId === row.packageId), 1)
     },
-    choosePackage(){
-      this.package.open=true;
-      this.package.title='疗法选择';
+    choosePackage() {
+      this.package.open = true;
+      this.package.title = '疗法选择';
     },
     /**
      * 选择课题
      */
-    chooseQuestionBank(){
-      this.questionBank.open=true;
-      this.questionBank.title='课题选择';
+    chooseQuestionBank() {
+      this.questionBank.open = true;
+      this.questionBank.title = '课题选择';
+    },
+
+    /**
+     * 选择拍商品
+     */
+    chooseCourseProduct() {
+      this.courseProduct.open = true;
+      this.courseProduct.title = '拍商品选择';
     },
 
     //选择疗法
-    selectPackage(row){
+    selectPackage(row) {
       const drug = {};
-      for(var i=0;i<this.packageList.length;i++){
-        if(this.packageList[i].packageId==row.packageId){
+      for (var i = 0; i < this.packageList.length; i++) {
+        if (this.packageList[i].packageId == row.packageId) {
           this.$message.warning("疗法已存在!")
           return;
         }
       }
-      drug.packageId=row.packageId;
-      drug.packageName=row.packageName;
-      drug.secondName=row.secondName;
-      drug.totalPrice=row.totalPrice;
-      drug.imgUrl=row.imgUrl;
+      drug.packageId = row.packageId;
+      drug.packageName = row.packageName;
+      drug.secondName = row.secondName;
+      drug.totalPrice = row.totalPrice;
+      drug.imgUrl = row.imgUrl;
       this.packageList.push(drug);
       this.$message({
         message: '添加成功',
@@ -575,14 +516,31 @@ export default {
 
     },
 
+    courseProductResult(val){
+      this.form.courseProducts = this.form.courseProducts || [];
+      for (var i = 0; i < this.form.courseProducts.length; i++) {
+        if (this.form.courseProducts[i].id == val.id) {
+          return this.$message.error("当前商品已选择")
+        }
+      }
+
+      //先置空 只选择一件商品
+      this.form.courseProducts = [];
+      this.form.courseProducts.push(val);
+      this.$message({
+        message: '添加成功',
+        type: 'success'
+      });
+    },
+
     //选择结果
-    questionBankResult(val){
+    questionBankResult(val) {
 
       // 确保 questionBankList 是数组
       this.form.questionBankList = this.form.questionBankList || [];
 
-      for(var i=0;i<this.form.questionBankList.length;i++){
-        if(this.form.questionBankList[i].id==val.id){
+      for (var i = 0; i < this.form.questionBankList.length; i++) {
+        if (this.form.questionBankList[i].id == val.id) {
           return this.$message.error("当前课题已选择")
         }
       }
@@ -595,15 +553,20 @@ export default {
     },
 
     //删除课题
-    handleQuestionBankDelete(row){
+    handleQuestionBankDelete(row) {
       this.form.questionBankList.splice(this.form.questionBankList.findIndex(item => item.id === row.id), 1)
     },
-       handleVideoChange(){
-      if(this.form.uploadType==1){
+
+    //删除商品
+    handleCourseProductDelete(row) {
+      this.form.courseProducts.splice(this.form.courseProducts.findIndex(item => item.id === row.id), 1)
+    },
+    handleVideoChange() {
+      if (this.form.uploadType == 1) {
         this.videoUrl = this.form.lineOne;
-      }else if(this.form.uploadType==2){
+      } else if (this.form.uploadType == 2) {
         this.videoUrl = this.form.lineTwo;
-      }else if(this.form.uploadType==3){
+      } else if (this.form.uploadType == 3) {
         this.videoUrl = this.form.lineThree;
       }
       // console.log("选择的video=======>>>>>>>",this.videoUrl)
@@ -615,7 +578,7 @@ export default {
         return
       }
 
-      const params = {ids: projectIds}
+      const params = { ids: projectIds }
       getByIds(params).then(response => {
         if (response.code === 200) {
           response.data.forEach(item => {
@@ -646,11 +609,11 @@ export default {
     },
 
     handleAvatarSuccess(res, file) {
-      if(res.code==200){
-        this.form.thumbnail=res.url;
+      if (res.code == 200) {
+        this.form.thumbnail = res.url;
         this.$forceUpdate()
       }
-      else{
+      else {
         this.msgError(res.msg);
       }
     },
@@ -659,9 +622,9 @@ export default {
       if (!isLt1M) {
         this.$message.error('上传图片大小不能超过 1MB!');
       }
-      return   isLt1M;
+      return isLt1M;
     },
-    getDetails(courseId,courseName,isPrivate) {
+    getDetails(courseId, courseName, isPrivate) {
       this.isPrivate = isPrivate
       this.courseName = courseName
       this.courseId = courseId;
@@ -691,11 +654,11 @@ export default {
         thumbnail: null,
         duration: null,
         createTime: null,
-        uploadType:null,
-        lineOne:null,
-        lineTwo:null,
-        lineThree:null,
-        fileName:null,
+        uploadType: null,
+        lineOne: null,
+        lineTwo: null,
+        lineThree: null,
+        fileName: null,
         userId: null,
         cateId: null,
         courseId: null,
@@ -716,14 +679,18 @@ export default {
         tags: null,
         productId: null,
         productJson: null,
-        questionBankId:null,
-        questionBankList:[],
-        redPacketMoney:0,
-        isTranscode:0,
-        transcodeFileKey:null
+        questionBankId: null,
+        questionBankList: [],
+        redPacketMoney: 0,
+        isTranscode: 0,
+        transcodeFileKey: null,
+        isProduct: 0,
+        productId: null,
+        listingStartTime: null,
+        listingEndTime: null,
       };
       this.videoURL = '';
-      this.progress=0;
+      this.progress = 0;
       this.resetForm("form");
     },
     /** 搜索按钮操作 */
@@ -739,20 +706,20 @@ export default {
     // 多选框选中数据
     handleSelectionChange(selection) {
       this.ids = selection.map(item => item.videoId)
-      this.single = selection.length!==1
+      this.single = selection.length !== 1
       this.multiple = !selection.length
     },
     // 多选框选中数据
     handVideoleSelectionChange(selection) {
       this.addBatchData.select = selection.map(item => item.id);
     },
-    handleAdd(){
+    handleAdd() {
       this.reset();
       this.form.courseId = this.courseId;
       this.open = true;
       this.title = "添加课堂视频";
       this.videoUrl = '';
-      this.packageList=[];
+      this.packageList = [];
       getSort(this.courseId).then(response => {
         this.form.courseSort = Number(response.data);
       })
@@ -764,18 +731,18 @@ export default {
     /** 修改按钮操作 */
     handleUpdate(row) {
       this.reset();
-      this.packageList=[];
+      this.packageList = [];
       const videoId = row.videoId || this.ids
       getUserCourseVideo(videoId).then(response => {
         this.form = response.data;
-        if(response.data.videoUrl!=null && response.data.videoUrl!==''){
+        if (response.data.videoUrl != null && response.data.videoUrl !== '') {
           this.videoUrl = response.data.videoUrl;
         }
-        if (this.form.packageJson!=null){
+        if (this.form.packageJson != null) {
           this.packageList = JSON.parse(this.form.packageJson);
         }
 
-        if(response.data.viewStartTime != null && response.data.viewEndTime != null){
+        if (response.data.viewStartTime != null && response.data.viewEndTime != null) {
           this.form.timeRange = [response.data.viewStartTime, response.data.viewEndTime]
         }
         setTimeout(() => {
@@ -792,24 +759,31 @@ export default {
 
         if (valid) {
           this.form.videoUrl = this.videoUrl;
-          if(this.form.videoUrl==null || this.form.videoUrl===''){
+          if (this.form.videoUrl == null || this.form.videoUrl === '') {
             this.$message({
               message: '请上传视频!',
               type: 'warning'
             });
             return
           }
-          if(this.form.timeRange != null && this.form.timeRange.length === 2){
+          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){
+          if (this.form.duration == null) {
             this.$message({
               message: '未识别到视频时长请稍等。。。',
               type: 'warning'
             });
             return
           }
+          if (this.form.isProduct != null && this.form.isProduct == 1 && this.form.courseProducts.length < 1) {
+             this.$message({
+              message: '请选择关联商品',
+              type: 'warning'
+            });
+            return
+          }
           // if(this.form.uploadType==null){
           //   this.$message({
           //     message: '请选择播放线路!',
@@ -817,12 +791,15 @@ export default {
           //   });
           //   return
           // }
-          if (this.form.questionBankList!==null){
+          if (this.form.questionBankList !== null) {
             this.form.questionBankId = this.form.questionBankList.map(item => item.id).join(',');
           }
-          if (this.packageList.length>0){
+          if (this.packageList.length > 0) {
             this.form.packageJson = JSON.stringify(this.packageList);
           }
+          if (this.form.courseProducts != null) {
+            this.form.productId = this.form.courseProducts[0].id
+          }
           if (this.form.videoId != null) {
             updateUserCourseVideo(this.form).then(response => {
               this.msgSuccess("修改成功");
@@ -839,14 +816,14 @@ export default {
         }
       });
     },
-    openUpdates(){
+    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){
+      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];
       }
@@ -863,14 +840,14 @@ export default {
         confirmButtonText: "确定",
         cancelButtonText: "取消",
         type: "warning"
-      }).then(function() {
+      }).then(function () {
         return delUserCourseVideo(videoIds);
       }).then(() => {
         this.getList();
         this.msgSuccess("删除成功");
-      }).catch(() => {});
+      }).catch(() => { });
     },
-    openAdds(){
+    openAdds() {
       this.addBatchData.open = true;
       this.getRootTypeList();
       this.addBatchData.form = {
@@ -893,7 +870,7 @@ export default {
         this.addBatchData.typeSubOptions = response.data
       })
     },
-    resourceList(){
+    resourceList() {
       this.addBatchData.loading = true;
       listVideoResource(this.addBatchData.queryParams).then(response => {
         this.addBatchData.loading = false;
@@ -901,8 +878,8 @@ export default {
         this.addBatchData.total = response.total;
       });
     },
-    batchVideoSave(){
-      if (this.addBatchData.select.length===0){
+    batchVideoSave() {
+      if (this.addBatchData.select.length === 0) {
         this.$message({
           message: '请选择视频!!',
           type: 'warning'
@@ -916,7 +893,7 @@ export default {
         this.getList();
       })
     },
-    updateRedPageckeOpen(){
+    updateRedPageckeOpen() {
       this.redData.open = true;
       this.redData.loading = true;
       this.redData.queryParams.courseId = this.courseId;
@@ -925,7 +902,7 @@ export default {
         this.redData.loading = false;
       });
     },
-    batchRedSave(){
+    batchRedSave() {
       batchUpdateRed(this.redData.list).then(response => {
         this.redData.open = false;
         this.getList();
@@ -949,6 +926,7 @@ export default {
   position: relative;
   overflow: hidden;
 }
+
 .avatar-uploader .el-upload:hover {
   border-color: #409EFF;
 }
@@ -961,5 +939,4 @@ export default {
   line-height: 150px;
   text-align: center;
 }
-
 </style>

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

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

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

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

+ 9 - 3
src/views/components/his/storeOrderDetails.vue

@@ -299,6 +299,7 @@
                         <el-option key="SF"  label="顺丰" value="SF" />
                         <el-option key="EMS"  label="邮政" value="EMS" />
                          <el-option key="ZTO"  label="中通" value="ZTO" />
+                         <el-option key="STO"  label="申通" value="ZTO" />
                          <el-option key="JD"  label="京东" value="JD" />
                          <el-option key="DBL"  label="德邦" value="DBL" />
 						 <el-option key="YD"  label="韵达" value="YD" />
@@ -366,7 +367,10 @@
                    </el-form-item>
                    <el-form-item label="详情地址" prop="userAddress"  >
                     <el-input v-model="editForm.userAddress" placeholder="请输入" />
-                  </el-form-item>
+                   </el-form-item>
+                   <el-form-item label="收货人电话" prop="userPhone"  >
+                    <el-input v-model="editForm.userPhone" placeholder="请输入" />
+                   </el-form-item>
                   <el-form-item label="备注" prop="remark"  >
                     <el-input v-model="editForm.remark" placeholder="请输入备注" />
                   </el-form-item>
@@ -418,7 +422,7 @@
 </template>
 
 <script>
-import {getMsgFollow,sendMsg,msgList,updateMoney,getGoods,getEroOrder,editTuiMoney,updateDelivery,createErpOrder,updateExpress,updateErp, afterSales,sendgoods,logList,listOrder,getExpress,syncExpress, listOrderitem,getOrder, delOrder, addOrder, updateOrder, exportOrder,payment,tuiOrder ,getPrescribe,getOrderUserPhone,returnCost} from "@/api/his/storeOrder";
+import {getMsgFollow,sendMsg,msgList,updateMoney,getGoods,getEroOrder,editTuiMoney,updateDelivery,createErpOrder,updateExpress,updateErp, afterSales,sendgoods,logList,listOrder,getExpress,syncExpress, listOrderitem,getOrder, delOrder, addOrder, updateStoreOrder, exportOrder,payment,tuiOrder ,getPrescribe,getOrderUserPhone,returnCost} from "@/api/his/storeOrder";
 import inquiryOrderDetails from '../his/inquiryOrderDetails.vue';
 import packageOrderDetails from '../his/packageOrderDetails2.vue';
 import prescribeDetails from '../his/prescribeDetails.vue';
@@ -515,6 +519,7 @@ import { getTcmScheduleList } from "@/api/company/schedule";
           userAddress:null,
           deliveryStatus:null,
           deliveryType:null,
+          userPhone:null,
           remark:"",
         },
         editDyRules:{
@@ -706,7 +711,7 @@ import { getTcmScheduleList } from "@/api/company/schedule";
     submitEditForm(){
         this.$refs["editForm"].validate(valid => {
         if (valid) {
-          updateOrder(this.editForm).then(response => {
+          updateStoreOrder(this.editForm).then(response => {
             if (response.code === 200) {
               this.msgSuccess("操作成功");
                this.edit.open = false;
@@ -725,6 +730,7 @@ import { getTcmScheduleList } from "@/api/company/schedule";
         this.editForm.orderId=this.item.orderId;
         this.editForm.remark=this.item.remark;
         this.editForm.userAddress = this.item.userAddress.toString();
+        this.editForm.userPhone = this.item.userPhone.toString();
         this.editForm.status = this.item.status.toString();
         this.editForm.deliveryType = this.item.deliveryType.toString();
         this.editForm.deliveryStatus = this.item.deliveryStatus.toString();

+ 165 - 0
src/views/course/fsCourseProduct/CourseProduct.vue

@@ -0,0 +1,165 @@
+<template>
+    <div class="app-container">
+        <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+            <el-form-item label="商品名称" prop="productName">
+                <el-input v-model="queryParams.productName" placeholder="请输入商品名称" clearable size="small"
+                    @keyup.enter.native="handleQuery" />
+            </el-form-item>
+
+            <el-form-item label="产品条码" prop="barCode">
+                <el-input v-model="queryParams.barCode" 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 border v-loading="loading" :data="fsCourseProductList">
+            <el-table-column label="商品名称" align="center" prop="productName" />
+            <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: 150px;">
+                    </el-popover>
+                </template>
+            </el-table-column>
+            <el-table-column label="产品条码" align="center" prop="barCode" />
+            <el-table-column label="商品价格" align="center" prop="productPrice" />
+            <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="chooseCourseProduct(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 { listFsCourseProduct, } from "@/api/course/fsCourseProduct";
+import Material from '@/components/Material'
+import singleImg from '@/components/Material/single'
+export default {
+    name: "CourseProduct",
+    components: { Material, singleImg, },
+    watch: {
+        imageArr: function (val) {
+            this.form.imgUrl = val.join(',')
+        },
+        photoArr: function (val) {
+            this.form.images = val.join(',')
+        }
+    },
+    data() {
+        return {
+            photoArr: [],
+            imageArr: [],
+            // 遮罩层
+            loading: true,
+            // 导出遮罩层
+            exportLoading: false,
+            // 选中数组
+            ids: [],
+            // 非单个禁用
+            single: true,
+            // 非多个禁用
+            multiple: true,
+            // 显示搜索条件
+            showSearch: true,
+            // 总条数
+            total: 0,
+            // 拍单商品表格数据
+            fsCourseProductList: [],
+            // 弹出层标题
+            title: "",
+            // 是否显示弹出层
+            open: false,
+            // 查询参数
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+                imgUrl: null,
+                images: null,
+                barCode: null,
+                sort: null,
+                stock: null,
+                productContent: null,
+                productPrice: null,
+                productName: null,
+            },
+            // 表单参数
+            form: {},
+            // 表单校验
+
+        };
+    },
+    created() {
+        this.getList();
+    },
+    methods: {
+        chooseCourseProduct(val) {
+            this.$emit('courseProductResult', val)
+        },
+        /** 查询拍单商品列表 */
+        getList() {
+            this.loading = true;
+            listFsCourseProduct(this.queryParams).then(response => {
+                this.fsCourseProductList = response.rows;
+                this.total = response.total;
+                this.loading = false;
+            });
+        },
+        // 取消按钮
+        cancel() {
+            this.open = false;
+            this.reset();
+        },
+        // 表单重置
+        reset() {
+            this.form = {
+                id: null,
+                imgUrl: null,
+                images: null,
+                barCode: null,
+                sort: null,
+                stock: null,
+                productContent: null,
+                productPrice: null,
+                productName: null,
+                createTime: null,
+                updateTime: null
+            };
+            this.resetForm("form");
+            this.photoArr = [];
+            this.imageArr = [];
+        },
+        /** 搜索按钮操作 */
+        handleQuery() {
+            this.queryParams.pageNum = 1;
+            this.getList();
+        },
+        /** 重置按钮操作 */
+        resetQuery() {
+            this.resetForm("queryForm");
+            this.handleQuery();
+        },
+
+
+
+
+
+
+    }
+};
+</script>

+ 316 - 0
src/views/course/fsCourseProduct/index.vue

@@ -0,0 +1,316 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="商品名称" prop="productName">
+        <el-input v-model="queryParams.productName" placeholder="请输入商品名称" clearable size="small"
+          @keyup.enter.native="handleQuery" />
+      </el-form-item>
+
+      <el-form-item label="产品条码" prop="barCode">
+        <el-input v-model="queryParams.barCode" 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="['course:fsCourseProduct: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:fsCourseProduct: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:fsCourseProduct: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:fsCourseProduct:export']">导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="fsCourseProductList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="商品名称" align="center" prop="productName" />
+      <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: 150px;">
+          </el-popover>
+        </template>
+      </el-table-column>
+      <el-table-column label="产品条码" align="center" prop="barCode" />
+      <el-table-column label="商品价格" align="center" prop="productPrice" />
+      <el-table-column label="排序" align="center" prop="sort" />
+      <el-table-column label="库存" align="center" prop="stock" />
+      <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:fsCourseProduct:edit']">修改</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+            v-hasPermi="['course:fsCourseProduct: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="900px" append-to-body>
+
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="商品名称" prop="productName">
+              <el-input v-model="form.productName" placeholder="请输入商品名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="商品价格" prop="productPrice">
+              <el-input v-model="form.productPrice" placeholder="请输入商品价格" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row>
+          <el-col :span="12">
+            <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="12">
+            <el-form-item label="库存" prop="stock">
+              <el-input-number v-model="form.stock" :min="0" label="库存"></el-input-number>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+
+        <el-form-item label="商品图片" prop="imgUrl">
+          <Material v-model="imageArr" type="image" :num="1" :width="150" :height="150" />
+        </el-form-item>
+        <el-form-item label="商品组图" prop="images">
+          <Material v-model="photoArr" type="image" :num="10" :width="150" :height="150" />
+        </el-form-item>
+
+        <el-form-item label="产品条码" prop="barCode">
+          <el-input v-model="form.barCode" placeholder="请输入产品条码" />
+        </el-form-item>
+
+        <el-form-item label="商品介绍">
+          <el-input v-model="form.productContent" type="textarea" placeholder="请输入商品介绍" />
+          <!-- <editor v-model="form.productContent" :min-height="192"/> -->
+        </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 { listFsCourseProduct, getFsCourseProduct, delFsCourseProduct, addFsCourseProduct, updateFsCourseProduct, exportFsCourseProduct } from "@/api/course/fsCourseProduct";
+import Material from '@/components/Material'
+import singleImg from '@/components/Material/single'
+export default {
+  name: "FsCourseProduct",
+  components: { Material, singleImg, },
+  watch: {
+    imageArr: function (val) {
+      this.form.imgUrl = val.join(',')
+    },
+    photoArr: function (val) {
+      this.form.images = val.join(',')
+    }
+  },
+  data() {
+    return {
+      photoArr: [],
+      imageArr: [],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 拍单商品表格数据
+      fsCourseProductList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        imgUrl: null,
+        images: null,
+        barCode: null,
+        sort: null,
+        stock: null,
+        productContent: null,
+        productPrice: null,
+        productName: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        productName: [
+          { required: true, message: "商品名称不能为空", trigger: "blur" }
+        ],
+        productPrice: [
+          { required: true, message: "商品价格不能为空", trigger: "blur" }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询拍单商品列表 */
+    getList() {
+      this.loading = true;
+      listFsCourseProduct(this.queryParams).then(response => {
+        this.fsCourseProductList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        imgUrl: null,
+        images: null,
+        barCode: null,
+        sort: null,
+        stock: null,
+        productContent: null,
+        productPrice: null,
+        productName: null,
+        createTime: null,
+        updateTime: null
+      };
+      this.resetForm("form");
+      this.photoArr = [];
+      this.imageArr = [];
+    },
+    /** 搜索按钮操作 */
+    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 = "添加拍单商品";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getFsCourseProduct(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改拍单商品";
+        if (this.form.imgUrl != null) {
+          this.imageArr = this.form.imgUrl.split(",");
+        }
+        if (this.form.images != null) {
+          this.photoArr = this.form.images.split(",");
+        }
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateFsCourseProduct(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addFsCourseProduct(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 delFsCourseProduct(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => { });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有拍单商品数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportFsCourseProduct(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => { });
+    }
+  }
+};
+</script>

+ 523 - 0
src/views/course/fsCourseProductOrder/index.vue

@@ -0,0 +1,523 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="订单编号" prop="orderCode">
+        <el-input
+          v-model="queryParams.orderCode"
+          placeholder="请输入订单编号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      
+      <el-form-item label="用户名称" prop="userName">
+        <el-input
+          v-model="queryParams.userName"
+          placeholder="请输入用户名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="用户电话" prop="userPhone">
+        <el-input
+          v-model="queryParams.userPhone"
+          placeholder="请输入用户电话"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+     
+      
+      
+      <el-form-item label="支付状态" prop="isPay">
+        <!-- <el-input
+          v-model="queryParams.isPay"
+          placeholder="请输入支付状态 0 待支付 1已支付"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        /> -->
+        <el-select v-model="queryParams.isPay" placeholder="请选择" clearable size="small">
+          <el-option label="待支付" value="0" />
+          <el-option label="已支付" value="1" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="支付时间" prop="payDateRange">
+        <el-date-picker
+          v-model="payDateRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          style="width: 240px"
+        />
+      </el-form-item>
+      <el-form-item label="订单状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择" clearable size="small">
+          <el-option label="待支付" value="1" />
+          <el-option label="已支付" value="2" />
+          <el-option label="已完成" value="3" />
+          <el-option label="申请退款" value="-1" />
+          <el-option label="已退款" value="-2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="退款时间" prop="refundDateRange">
+       <el-date-picker
+          v-model="refundDateRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          style="width: 240px"
+        />
+      </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="['fsCourseProductOrder:fsCourseProductOrder: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="['fsCourseProductOrder:fsCourseProductOrder: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="['fsCourseProductOrder:fsCourseProductOrder: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:fsCourseProductOrder:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="fsCourseProductOrderList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="订单编号" width="180px" align="center" prop="orderCode" />
+      <el-table-column label="用户名称" align="center" prop="userName" />
+      <el-table-column label="用户电话" width="180px" align="center" prop="userPhone" >
+        <template slot-scope="scope">
+          <span>{{ scope.row.userPhone }}<el-button icon="el-icon-search" size="mini" @click="handlePhone(scope)" style="margin-left: 20px;" circle v-hasPermi="['course:fsCourseProductOrder:queryPhone']"></el-button></span>
+        </template>
+      </el-table-column>
+      <el-table-column label="商品名称" align="center" prop="productName"/>
+      <el-table-column label="课程" align="center" :show-overflow-tooltip="true" prop="courseName" />
+      <el-table-column label="视频" align="center" :show-overflow-tooltip="true" prop="title" />
+      <el-table-column label="公司名称" align="center"  prop="companyName" />
+      <el-table-column label="销售" align="center"  prop="companyUserName" />  
+      <el-table-column label="企微外部联系人" align="center"  prop="extName" />      
+      <el-table-column label="支付状态" align="center" prop="isPay">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.isPay == 0">待支付</el-tag>
+          <el-tag v-if="scope.row.isPay == 1" type="success">已支付</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="支付时间" align="center" prop="payTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.payTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="订单状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <span v-if="scope.row.status === 1">待支付</span>
+          <span v-if="scope.row.status === 2">已支付</span>
+          <span v-if="scope.row.status === 3">已完成</span>
+          <span v-if="scope.row.status === -1">申请退款</span>
+          <span v-if="scope.row.status === -2">退款成功</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="申请退款时间" align="center" prop="refundTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.refundTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="申请退款理由" :show-overflow-tooltip="true" align="center" prop="refundExplain" />
+      <el-table-column label="核销码" align="center" prop="verifyCode" />
+      
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            :disabled="scope.row.status == 1 || scope.row.status == 3 || scope.row.status == -2"
+            size="mini"
+            type="text"
+            @click="handleRefund(scope.row)"
+            v-hasPermi="['course:fsCourseProductOrder:refund']"
+          >退款</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="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="订单编号" prop="orderCode">
+          <el-input v-model="form.orderCode" placeholder="请输入订单编号" />
+        </el-form-item>
+        <el-form-item label="用户id" prop="userId">
+          <el-input v-model="form.userId" placeholder="请输入用户id" />
+        </el-form-item>
+        <el-form-item label="用户名称" prop="userName">
+          <el-input v-model="form.userName" placeholder="请输入用户名称" />
+        </el-form-item>
+        <el-form-item label="用户电话" prop="userPhone">
+          <el-input v-model="form.userPhone" placeholder="请输入用户电话" />
+        </el-form-item>
+        <el-form-item label="拍单商品id" prop="courseProductId">
+          <el-input v-model="form.courseProductId" placeholder="请输入拍单商品id" />
+        </el-form-item>
+        <el-form-item label="商品json" prop="productJson">
+          <el-input v-model="form.productJson" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+        <el-form-item label="课程id" prop="courseId">
+          <el-input v-model="form.courseId" placeholder="请输入课程id" />
+        </el-form-item>
+        <el-form-item label="视频小节id" prop="videoId">
+          <el-input v-model="form.videoId" placeholder="请输入视频小节id" />
+        </el-form-item>
+        <el-form-item label="支付状态 0 待支付 1已支付" prop="isPay">
+          <el-input v-model="form.isPay" placeholder="请输入支付状态 0 待支付 1已支付" />
+        </el-form-item>
+        <el-form-item label="支付时间" prop="payTime">
+          <el-date-picker clearable size="small"
+            v-model="form.payTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择支付时间">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="支付方式 微信" prop="payType">
+          <el-select v-model="form.payType" placeholder="请选择支付方式 微信">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="1 待支付 2已支付 3 已完成 -1 申请退款 -2 : 退款成功">
+          <el-radio-group v-model="form.status">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="申请退款时间" prop="refundTime">
+          <el-date-picker clearable size="small"
+            v-model="form.refundTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择申请退款时间">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="申请退款理由" prop="refundExplain">
+          <el-input v-model="form.refundExplain" placeholder="请输入申请退款理由" />
+        </el-form-item>
+        <el-form-item label="核销码" prop="verifyCode">
+          <el-input v-model="form.verifyCode" placeholder="请输入核销码" />
+        </el-form-item>
+        <el-form-item label="公司id" prop="companyId">
+          <el-input v-model="form.companyId" placeholder="请输入公司id" />
+        </el-form-item>
+        <el-form-item label="销售id" prop="companyUserId">
+          <el-input v-model="form.companyUserId" placeholder="请输入销售id" />
+        </el-form-item>
+        <el-form-item label="企微外部联系人id" prop="extId">
+          <el-input v-model="form.extId" placeholder="请输入企微外部联系人id" />
+        </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 { listFsCourseProductOrder, getFsCourseProductOrder, delFsCourseProductOrder, addFsCourseProductOrder, updateFsCourseProductOrder, exportFsCourseProductOrder, refund, getOrderUserPhone } from "@/api/course/fsCourseProductOrder";
+
+export default {
+  name: "FsCourseProductOrder",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 拍单商品订单表格数据
+      fsCourseProductOrderList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      payDateRange: [],
+      refundDateRange: [],
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        orderCode: null,
+        userId: null,
+        userName: null,
+        userPhone: null,
+        courseProductId: null,
+        productJson: null,
+        courseId: null,
+        videoId: null,
+        isPay: null,
+        payStartTime: null,
+        payEndTime: null,
+        payType: null,
+        status: null,
+        refundStartTime: null,
+        refundEndTime: null,
+        refundExplain: null,
+        verifyCode: null,
+        companyId: null,
+        companyUserId: null,
+        extId: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询拍单商品订单列表 */
+    getList() {
+      this.loading = true;
+      if (this.payDateRange && this.payDateRange.length === 2) {
+        this.queryParams.payStartTime = this.payDateRange[0];
+        this.queryParams.payEndTime = this.payDateRange[1].substring(0,10)+" 23:59:59";
+      } else {
+        this.queryParams.payStartTime = null;
+        this.queryParams.payEndTime = null;
+      }
+      if (this.refundDateRange && this.refundDateRange.length === 2) {
+        this.queryParams.refundStartTime = this.refundDateRange[0];
+        this.queryParams.refundEndTime = this.refundDateRange[1].substring(0,10)+" 23:59:59";
+      } else {
+        this.queryParams.refundStartTime = null;
+        this.queryParams.refundEndTime = null;
+      }
+      listFsCourseProductOrder(this.queryParams).then(response => {
+        this.fsCourseProductOrderList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        courseOrderId: null,
+        orderCode: null,
+        userId: null,
+        userName: null,
+        userPhone: null,
+        courseProductId: null,
+        productJson: null,
+        courseId: null,
+        videoId: null,
+        isPay: null,
+        payTime: null,
+        payType: null,
+        status: 0,
+        createTime: null,
+        refundTime: null,
+        refundExplain: null,
+        verifyCode: null,
+        updateTime: null,
+        companyId: null,
+        companyUserId: null,
+        extId: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.payDateRange = [];
+      this.refundDateRange = [];
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.courseOrderId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加拍单商品订单";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const courseOrderId = row.courseOrderId || this.ids
+      getFsCourseProductOrder(courseOrderId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改拍单商品订单";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.courseOrderId != null) {
+            updateFsCourseProductOrder(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addFsCourseProductOrder(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const courseOrderIds = row.courseOrderId || this.ids;
+      this.$confirm('是否确认删除拍单商品订单编号为"' + courseOrderIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delFsCourseProductOrder(courseOrderIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      if (this.payDateRange && this.payDateRange.length === 2) {
+        this.queryParams.payStartTime = this.payDateRange[0];
+        this.queryParams.payEndTime = this.payDateRange[1];
+      } else {
+        this.queryParams.payStartTime = null;
+        this.queryParams.payEndTime = null;
+      }
+      if (this.refundDateRange && this.refundDateRange.length === 2) {
+        this.queryParams.refundStartTime = this.refundDateRange[0];
+        this.queryParams.refundEndTime = this.refundDateRange[1];
+      } else {
+        this.queryParams.refundStartTime = null;
+        this.queryParams.refundEndTime = null;
+      }
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有拍单商品订单数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportFsCourseProductOrder(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+
+    /**申请退款 */
+    handleRefund(row) {
+      const data = {
+        courseOrderId : row.courseOrderId
+      };
+       this.$confirm('是否确认拍商品退款?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          return refund(data);
+        }).then(response => {
+          this.getList();
+          this.msgSuccess("退款成功");
+        }).catch(() => {});
+    },
+
+    /**查询电话 */
+    handlePhone(scope) {
+      getOrderUserPhone(scope.row.courseOrderId).then(res=>{
+        scope.row.userPhone = res.userPhone
+      }) 
+    },
+  }
+};
+</script>

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

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

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

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

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

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

+ 3 - 4
src/views/course/userCoursePeriod/batchRedPacket.vue

@@ -24,7 +24,7 @@
         align="center"
       />
       <el-table-column
-        label="金额(0.1-0.3元)"
+        label="金额"
         align="center"
         width="200"
       >
@@ -32,7 +32,6 @@
           <el-input-number
             v-model="scope.row.amount"
             :min="0.1"
-            :max="0.3"
             :precision="2"
             :step="0.01"
             size="small"
@@ -96,9 +95,9 @@ export default {
     // 保存
     handleSave() {
       // 验证金额范围
-      const invalidItems = this.tableData.filter(item => item.amount < 0.1 || item.amount > 0.3);
+      const invalidItems = this.tableData.filter(item => item.amount < 0.1);
       if (invalidItems.length > 0) {
-        this.$message.error('红包金额需要在0.1元至0.3元之间');
+        this.$message.error('红包金额需要大于等于0.1元');
         return;
       }
 

+ 27 - 11
src/views/course/userCoursePeriod/index.vue

@@ -165,6 +165,7 @@
           <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="periodLine" width="180" />
           <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" />
@@ -249,12 +250,12 @@
         <el-form-item label="课程LOGO" prop="courseLogo">
           <image-upload v-model="form.courseLogo" :limit="1" />
         </el-form-item>
-        <el-form-item label="红包发放方式" prop="redPacketGrantMethod">
-          <el-radio-group v-model="form.redPacketGrantMethod">
-            <el-radio :label="1" >按课程</el-radio>
-            <el-radio :label="2" >按营期</el-radio>
-          </el-radio-group>
-        </el-form-item>
+<!--        <el-form-item label="红包发放方式" prop="redPacketGrantMethod">-->
+<!--          <el-radio-group v-model="form.redPacketGrantMethod">-->
+<!--            <el-radio :label="1" >按课程</el-radio>-->
+<!--            <el-radio :label="2" >按营期</el-radio>-->
+<!--          </el-radio-group>-->
+<!--        </el-form-item>-->
         <el-form-item label="营期类型" prop="periodType">
           <el-radio-group v-model="form.periodType">
             <el-radio :label="1" >多课程</el-radio>
@@ -291,6 +292,21 @@
           >
           </el-date-picker>
         </el-form-item>
+        <el-form-item prop="periodLine">
+          <span slot="label">
+            营期线
+            <el-tooltip content="营期首次播放课程的日期" placement="top">
+              <i class="el-icon-question" style="margin-left: 5px; color: #909399; cursor: help;"></i>
+            </el-tooltip>
+          </span>
+          <el-date-picker
+            v-model="form.periodLine"
+            type="date"
+            placeholder="请选择营期线"
+            value-format="yyyy-MM-dd"
+            :clearable="true">
+          </el-date-picker>
+        </el-form-item>
       </el-form>
       <div class="drawer-footer">
         <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -704,9 +720,9 @@ export default {
         courseStyle: [
           { required: true, message: '课程风格不能为空', trigger: 'change' }
         ],
-        redPacketGrantMethod: [
-          { required: true, message: '红包发放方式不能为空', trigger: 'change' }
-        ],
+        // redPacketGrantMethod: [
+        //   { required: true, message: '红包发放方式不能为空', trigger: 'change' }
+        // ],
         periodType: [
           { required: true, message: '营期类型不能为空', trigger: 'change' }
         ],
@@ -1413,8 +1429,8 @@ export default {
       this.course.form = {
         periodId: this.course.queryParams.periodId,
         courseId: null,
-        timeRange: null,
-        joinTime: null,
+        timeRange: ['00:00:00', '23:59:59'],
+        joinTime: '23:59:59',
         videoIds: []
       };
       // 重置表单

+ 6 - 9
src/views/course/userCoursePeriod/redPacket.vue

@@ -27,12 +27,11 @@
         <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="红包金额(0.0-0.3元)" width="200px" align="center">
+        <el-table-column label="红包金额" width="200px" align="center">
           <template slot-scope="scope">
             <el-input-number
               v-model="scope.row.amount"
-              :min="0.0"
-              :max="0.3"
+              :min="0.1"
               :precision="2"
               :step="0.01"
               size="small"
@@ -129,27 +128,25 @@ export default {
         periodId: this.periodId,
         companyId: this.currentCompany.companyId
       }).then(response => {
-        console.log("-----------",JSON.stringify(response.data))  
         this.redPacketList = (response.data || []).map(item => ({
           ...item,
-          // amount: item.amount || 0.1
-          amount:item.amount ?? 0.1
+          amount: item.amount || 0.1
         }));
       });
     },
     // 保存红包金额
     handleSave() {
       // 筛选出有金额的项目
-      const validAmountItems = this.redPacketList.filter(item => item.amount >= 0);
+      const validAmountItems = this.redPacketList.filter(item => item.amount > 0);
       if (validAmountItems.length === 0) {
         this.$message.warning('请至少设置一个红包金额');
         return;
       }
 
       // 验证金额范围
-      const invalidItems = validAmountItems.filter(item => item.amount < 0.0 || item.amount > 0.3);
+      const invalidItems = validAmountItems.filter(item => item.amount < 0.1);
       if (invalidItems.length > 0) {
-        this.$message.error('红包金额需要在0.0元至0.3元之间');
+        this.$message.error('红包金额需要大于等于0.1元');
         return;
       }
 

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

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

+ 390 - 0
src/views/course/userWatchCourseStatistics/index.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="periodName">
+        <el-input
+          v-model="queryParams.periodName"
+          placeholder="请输入营期名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="营期线" prop="periodStartingTime">
+        <el-date-picker clearable size="small"
+                        v-model="queryParams.periodStartingTime"
+                        type="date"
+                        value-format="yyyy-MM-dd"
+                        placeholder="选择营期线">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="播出时间" prop="courseStartDateTime">
+        <el-date-picker clearable size="small"
+                        v-model="queryParams.courseStartDateTime"
+                        type="date"
+                        value-format="yyyy-MM-dd"
+                        placeholder="选择课程开始日期">
+        </el-date-picker>
+      </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="companyName">
+        <el-input
+          v-model="queryParams.companyName"
+          placeholder="请输入销售公司名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="所属销售" prop="companyUserName">
+        <el-input
+          v-model="queryParams.companyUserName"
+          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="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['course:userWatchCourseStatistics:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="userWatchCourseStatisticsList" @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="periodStartingTime" />
+      <el-table-column label="播出时间" align="center" prop="courseStartDateTime" />
+      <el-table-column label="课程名称" align="center" prop="courseName" />
+      <el-table-column label="视频小节" align="center" prop="videoTitle" />
+      <el-table-column label="销售公司" align="center" prop="companyName" />
+      <el-table-column label="所属销售" align="center" prop="companyUserName" />
+      <el-table-column label="新增会员" align="center" prop="newUserNum" />
+      <el-table-column label="会员数量" align="center" prop="userNum" />
+      <el-table-column label="观看人数" align="center" prop="watchNum" />
+      <el-table-column label="上线率" align="center" prop="onlineRatePercent" />
+      <el-table-column label="完播人数" align="center" prop="completeWatchNum" />
+      <el-table-column label="完播率" align="center" prop="completeWatchRatePercent" />
+      <el-table-column label="答题人数" align="center" prop="answerNum" />
+      <el-table-column label="正确人数" align="center" prop="answerRightNum" />
+      <el-table-column label="正确率" align="center" prop="answerRightRatePercent" />
+      <el-table-column label="红包领取个数" align="center" prop="redPacketNum" />
+      <el-table-column label="红包领取总额" align="center" prop="redPacketAmount" />
+    </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="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="营期id" prop="periodId">
+          <el-input v-model="form.periodId" placeholder="请输入营期id" />
+        </el-form-item>
+        <el-form-item label="营期名称" prop="periodName">
+          <el-input v-model="form.periodName" placeholder="请输入营期名称" />
+        </el-form-item>
+        <el-form-item label="课程id" prop="courseId">
+          <el-input v-model="form.courseId" placeholder="请输入课程id" />
+        </el-form-item>
+        <el-form-item label="课程名称" prop="courseName">
+          <el-input v-model="form.courseName" placeholder="请输入课程名称" />
+        </el-form-item>
+        <el-form-item label="视频id" prop="videoId">
+          <el-input v-model="form.videoId" placeholder="请输入视频id" />
+        </el-form-item>
+        <el-form-item label="视频标题" prop="videoTitle">
+          <el-input v-model="form.videoTitle" placeholder="请输入视频标题" />
+        </el-form-item>
+        <el-form-item label="销售公司id" prop="companyId">
+          <el-input v-model="form.companyId" placeholder="请输入销售公司id" />
+        </el-form-item>
+        <el-form-item label="销售公司名称" prop="companyName">
+          <el-input v-model="form.companyName" placeholder="请输入销售公司名称" />
+        </el-form-item>
+        <el-form-item label="销售id" prop="companyUserId">
+          <el-input v-model="form.companyUserId" placeholder="请输入销售id" />
+        </el-form-item>
+        <el-form-item label="课程开始日期" prop="courseStartDateTime">
+          <el-date-picker clearable size="small"
+            v-model="form.courseStartDateTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择课程开始日期">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="销售名称" prop="companyUserName">
+          <el-input v-model="form.companyUserName" placeholder="请输入销售名称" />
+        </el-form-item>
+        <el-form-item label="营期开始日期" prop="periodStartingTime">
+          <el-date-picker clearable size="small"
+            v-model="form.periodStartingTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择营期开始日期">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="新增会员数量" prop="newUserNum">
+          <el-input v-model="form.newUserNum" placeholder="请输入新增会员数量" />
+        </el-form-item>
+        <el-form-item label="会员数量" prop="userNum">
+          <el-input v-model="form.userNum" placeholder="请输入会员数量" />
+        </el-form-item>
+        <el-form-item label="观看人数" prop="watchNum">
+          <el-input v-model="form.watchNum" placeholder="请输入观看人数" />
+        </el-form-item>
+        <el-form-item label="完播人数" prop="completeWatchNum">
+          <el-input v-model="form.completeWatchNum" placeholder="请输入完播人数" />
+        </el-form-item>
+        <el-form-item label="完播率" prop="completeWatchRate">
+          <el-input v-model="form.completeWatchRate" placeholder="请输入完播率" />
+        </el-form-item>
+        <el-form-item label="答题人数" prop="answerNum">
+          <el-input v-model="form.answerNum" placeholder="请输入答题人数" />
+        </el-form-item>
+        <el-form-item label="答题正确人数" prop="answerRightNum">
+          <el-input v-model="form.answerRightNum" placeholder="请输入答题正确人数" />
+        </el-form-item>
+        <el-form-item label="答题正确率" prop="answerRightRate">
+          <el-input v-model="form.answerRightRate" placeholder="请输入答题正确率" />
+        </el-form-item>
+        <el-form-item label="红包领取个数" prop="redPacketNum">
+          <el-input v-model="form.redPacketNum" placeholder="请输入红包领取数量" />
+        </el-form-item>
+        <el-form-item label="红包领取总额" prop="redPacketAmount">
+          <el-input v-model="form.redPacketAmount" placeholder="请输入红包领取总额" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listUserWatchCourseStatistics, getUserWatchCourseStatistics, delUserWatchCourseStatistics, addUserWatchCourseStatistics, updateUserWatchCourseStatistics, exportUserWatchCourseStatistics } from "@/api/course/userWatchCourseStatistics";
+
+export default {
+  name: "UserWatchCourseStatistics",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 会员看课统计-按课程统计表格数据
+      userWatchCourseStatisticsList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        periodId: null,
+        periodName: null,
+        courseId: null,
+        courseName: null,
+        videoId: null,
+        videoTitle: null,
+        companyId: null,
+        companyName: null,
+        companyUserId: null,
+        courseStartDateTime: null,
+        companyUserName: null,
+        periodStartingTime: null,
+        newUserNum: null,
+        userNum: null,
+        watchNum: null,
+        completeWatchNum: null,
+        completeWatchRate: null,
+        answerNum: null,
+        answerRightNum: null,
+        answerRightRate: null,
+        redPacketNum: null,
+        redPacketAmount: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询会员看课统计-按课程统计列表 */
+    getList() {
+      this.loading = true;
+      listUserWatchCourseStatistics(this.queryParams).then(response => {
+        this.userWatchCourseStatisticsList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        periodId: null,
+        periodName: null,
+        courseId: null,
+        courseName: null,
+        videoId: null,
+        videoTitle: null,
+        companyId: null,
+        companyName: null,
+        companyUserId: null,
+        courseStartDateTime: null,
+        companyUserName: null,
+        periodStartingTime: null,
+        newUserNum: null,
+        userNum: null,
+        watchNum: null,
+        completeWatchNum: null,
+        completeWatchRate: null,
+        answerNum: null,
+        answerRightNum: null,
+        answerRightRate: null,
+        redPacketNum: null,
+        redPacketAmount: 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 = "添加会员看课统计-按课程统计";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getUserWatchCourseStatistics(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改会员看课统计-按课程统计";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateUserWatchCourseStatistics(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addUserWatchCourseStatistics(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 delUserWatchCourseStatistics(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出当前数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportUserWatchCourseStatistics(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 394 - 0
src/views/course/userWatchCourseTotalStatistics/index.vue

@@ -0,0 +1,394 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+
+      <el-form-item label="营期名称" prop="periodName">
+        <el-input
+          v-model="queryParams.periodName"
+          placeholder="请输入营期名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="营期线" prop="periodStartingTime">
+        <el-date-picker clearable size="small"
+                        v-model="queryParams.periodStartingTime"
+                        type="date"
+                        value-format="yyyy-MM-dd"
+                        placeholder="选择营期线">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="播出时间" prop="courseStartDateTime">
+        <el-date-picker clearable size="small"
+                        v-model="queryParams.courseStartDateTime"
+                        type="date"
+                        value-format="yyyy-MM-dd"
+                        placeholder="选择课程开始日期">
+        </el-date-picker>
+      </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="videoTitle">
+        <el-input
+          v-model="queryParams.videoTitle"
+          placeholder="请输入视频标题"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="销售公司" prop="companyName">
+        <el-input
+          v-model="queryParams.companyName"
+          placeholder="请输入销售公司名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+<!--      <el-form-item label="所属销售" prop="companyUserName">-->
+<!--        <el-input-->
+<!--          v-model="queryParams.companyUserName"-->
+<!--          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="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="userWatchCourseStatisticsList" @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="periodStartingTime" />
+      <el-table-column label="播出时间" align="center" prop="courseStartDateTime" />
+      <el-table-column label="课程名称" align="center" prop="courseName" />
+      <el-table-column label="视频小节" align="center" prop="videoTitle" />
+      <el-table-column label="销售公司" align="center" prop="companyName" />
+<!--      <el-table-column label="所属销售" align="center" prop="companyUserName" />-->
+      <el-table-column label="新增会员" align="center" prop="newUserNum" />
+      <el-table-column label="会员数量" align="center" prop="userNum" />
+      <el-table-column label="观看人数" align="center" prop="watchNum" />
+      <el-table-column label="上线率" align="center" prop="onlineRatePercent" />
+      <el-table-column label="完播人数" align="center" prop="completeWatchNum" />
+      <el-table-column label="完播率" align="center" prop="completeWatchRatePercent" />
+      <el-table-column label="红包领取个数" align="center" prop="redPacketNum" />
+    </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="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="营期id" prop="periodId">
+          <el-input v-model="form.periodId" placeholder="请输入营期id" />
+        </el-form-item>
+        <el-form-item label="营期名称" prop="periodName">
+          <el-input v-model="form.periodName" placeholder="请输入营期名称" />
+        </el-form-item>
+        <el-form-item label="课程id" prop="courseId">
+          <el-input v-model="form.courseId" placeholder="请输入课程id" />
+        </el-form-item>
+        <el-form-item label="课程名称" prop="courseName">
+          <el-input v-model="form.courseName" placeholder="请输入课程名称" />
+        </el-form-item>
+        <el-form-item label="视频id" prop="videoId">
+          <el-input v-model="form.videoId" placeholder="请输入视频id" />
+        </el-form-item>
+        <el-form-item label="视频标题" prop="videoTitle">
+          <el-input v-model="form.videoTitle" placeholder="请输入视频标题" />
+        </el-form-item>
+        <el-form-item label="销售公司id" prop="companyId">
+          <el-input v-model="form.companyId" placeholder="请输入销售公司id" />
+        </el-form-item>
+        <el-form-item label="销售公司名称" prop="companyName">
+          <el-input v-model="form.companyName" placeholder="请输入销售公司名称" />
+        </el-form-item>
+        <el-form-item label="销售id" prop="companyUserId">
+          <el-input v-model="form.companyUserId" placeholder="请输入销售id" />
+        </el-form-item>
+        <el-form-item label="课程开始日期" prop="courseStartDateTime">
+          <el-date-picker clearable size="small"
+            v-model="form.courseStartDateTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择课程开始日期">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="销售名称" prop="companyUserName">
+          <el-input v-model="form.companyUserName" placeholder="请输入销售名称" />
+        </el-form-item>
+        <el-form-item label="营期开始日期" prop="periodStartingTime">
+          <el-date-picker clearable size="small"
+            v-model="form.periodStartingTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择营期开始日期">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="新增会员数量" prop="newUserNum">
+          <el-input v-model="form.newUserNum" placeholder="请输入新增会员数量" />
+        </el-form-item>
+        <el-form-item label="会员数量" prop="userNum">
+          <el-input v-model="form.userNum" placeholder="请输入会员数量" />
+        </el-form-item>
+        <el-form-item label="观看人数" prop="watchNum">
+          <el-input v-model="form.watchNum" placeholder="请输入观看人数" />
+        </el-form-item>
+        <el-form-item label="完播人数" prop="completeWatchNum">
+          <el-input v-model="form.completeWatchNum" placeholder="请输入完播人数" />
+        </el-form-item>
+        <el-form-item label="完播率" prop="completeWatchRate">
+          <el-input v-model="form.completeWatchRate" placeholder="请输入完播率" />
+        </el-form-item>
+        <el-form-item label="答题人数" prop="answerNum">
+          <el-input v-model="form.answerNum" placeholder="请输入答题人数" />
+        </el-form-item>
+        <el-form-item label="答题正确人数" prop="answerRightNum">
+          <el-input v-model="form.answerRightNum" placeholder="请输入答题正确人数" />
+        </el-form-item>
+        <el-form-item label="答题正确率" prop="answerRightRate">
+          <el-input v-model="form.answerRightRate" placeholder="请输入答题正确率" />
+        </el-form-item>
+        <el-form-item label="红包领取个数" prop="redPacketNum">
+          <el-input v-model="form.redPacketNum" placeholder="请输入红包领取数量" />
+        </el-form-item>
+        <el-form-item label="红包领取总额" prop="redPacketAmount">
+          <el-input v-model="form.redPacketAmount" placeholder="请输入红包领取总额" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listUserWatchCourseStatisticsTotal, getUserWatchCourseStatistics, delUserWatchCourseStatistics, addUserWatchCourseStatistics, updateUserWatchCourseStatistics, exportUserWatchCourseStatisticsTotal } from "@/api/course/userWatchCourseStatistics";
+
+export default {
+  name: "UserWatchCourseStatistics",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 会员看课统计-按课程统计表格数据
+      userWatchCourseStatisticsList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        periodId: null,
+        periodName: null,
+        courseId: null,
+        courseName: null,
+        videoId: null,
+        videoTitle: null,
+        companyId: null,
+        companyName: null,
+        companyUserId: null,
+        courseStartDateTime: null,
+        companyUserName: null,
+        periodStartingTime: null,
+        newUserNum: null,
+        userNum: null,
+        watchNum: null,
+        completeWatchNum: null,
+        completeWatchRate: null,
+        answerNum: null,
+        answerRightNum: null,
+        answerRightRate: null,
+        redPacketNum: null,
+        redPacketAmount: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询会员看课统计-按课程统计列表 */
+    getList() {
+      this.loading = true;
+      listUserWatchCourseStatisticsTotal(this.queryParams).then(response => {
+        this.userWatchCourseStatisticsList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        periodId: null,
+        periodName: null,
+        courseId: null,
+        courseName: null,
+        videoId: null,
+        videoTitle: null,
+        companyId: null,
+        companyName: null,
+        companyUserId: null,
+        courseStartDateTime: null,
+        companyUserName: null,
+        periodStartingTime: null,
+        newUserNum: null,
+        userNum: null,
+        watchNum: null,
+        completeWatchNum: null,
+        completeWatchRate: null,
+        answerNum: null,
+        answerRightNum: null,
+        answerRightRate: null,
+        redPacketNum: null,
+        redPacketAmount: 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 = "添加会员看课统计-按课程统计";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getUserWatchCourseStatistics(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改会员看课统计-按课程统计";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateUserWatchCourseStatistics(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addUserWatchCourseStatistics(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 delUserWatchCourseStatistics(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出当前数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportUserWatchCourseStatisticsTotal(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 286 - 0
src/views/course/userWatchStatistics/index.vue

@@ -0,0 +1,286 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+
+      <el-form-item label="营期名称" prop="periodName">
+        <el-input
+          v-model="queryParams.periodName"
+          placeholder="请输入营期名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="营期线" prop="periodStartingTime">
+        <el-date-picker clearable size="small"
+          v-model="queryParams.periodStartingTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择营期线">
+        </el-date-picker>
+      </el-form-item>
+
+      <el-form-item label="销售公司" prop="companyName">
+        <el-input
+          v-model="queryParams.companyName"
+          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="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['course:userWatchStatistics:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="userWatchStatisticsList" @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="periodStartingTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.periodStartingTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="销售公司" align="center" prop="companyName" />
+      <el-table-column label="新增会员" align="center" prop="newUserNum" />
+      <el-table-column label="会员数量" align="center" prop="userNum" />
+      <el-table-column label="观看人数" align="center" prop="watchNum" />
+      <el-table-column label="上线率" align="center" prop="onlineRatePercent" />
+      <el-table-column label="完播人数" align="center" prop="completeWatchNum" />
+      <el-table-column label="完播率" align="center" prop="completeWatchRatePercent" />
+
+    </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="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="营期id" prop="periodId">
+          <el-input v-model="form.periodId" placeholder="请输入营期id" />
+        </el-form-item>
+        <el-form-item label="营期名称" prop="periodName">
+          <el-input v-model="form.periodName" placeholder="请输入营期名称" />
+        </el-form-item>
+        <el-form-item label="营期开始日期" prop="periodStartingTime">
+          <el-date-picker clearable size="small"
+            v-model="form.periodStartingTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择营期开始日期">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="新增会员数量" prop="newUserNum">
+          <el-input v-model="form.newUserNum" placeholder="请输入新增会员数量" />
+        </el-form-item>
+        <el-form-item label="会员数量" prop="userNum">
+          <el-input v-model="form.userNum" placeholder="请输入会员数量" />
+        </el-form-item>
+        <el-form-item label="观看人数" prop="watchNum">
+          <el-input v-model="form.watchNum" placeholder="请输入观看人数" />
+        </el-form-item>
+        <el-form-item label="完播人数" prop="completeWatchNum">
+          <el-input v-model="form.completeWatchNum" placeholder="请输入完播人数" />
+        </el-form-item>
+        <el-form-item label="完播率" prop="completeWatchRate">
+          <el-input v-model="form.completeWatchRate" placeholder="请输入完播率" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listUserWatchStatistics, getUserWatchStatistics, delUserWatchStatistics, addUserWatchStatistics, updateUserWatchStatistics, exportUserWatchStatistics } from "@/api/course/userWatchStatistics";
+
+export default {
+  name: "UserWatchStatistics",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 会员看课统计-按营期统计表格数据
+      userWatchStatisticsList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        periodId: null,
+        periodName: null,
+        periodStartingTime: null,
+        companyName: null,
+        newUserNum: null,
+        userNum: null,
+        watchNum: null,
+        completeWatchNum: null,
+        completeWatchRate: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询会员看课统计-按营期统计列表 */
+    getList() {
+      this.loading = true;
+      listUserWatchStatistics(this.queryParams).then(response => {
+        this.userWatchStatisticsList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        periodId: null,
+        periodName: null,
+        periodStartingTime: null,
+        newUserNum: null,
+        userNum: null,
+        watchNum: null,
+        completeWatchNum: null,
+        completeWatchRate: 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 = "添加会员看课统计-按营期统计";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getUserWatchStatistics(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改会员看课统计-按营期统计";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateUserWatchStatistics(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addUserWatchStatistics(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 delUserWatchStatistics(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出当前数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportUserWatchStatistics(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

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

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

+ 287 - 0
src/views/his/bill/redPackage/index.vue

@@ -0,0 +1,287 @@
+<template>
+    <div class="app-container">
+      <el-card>
+        <div slot="header" class="clearfix">
+          <span>红包金额统计</span>
+        </div>
+
+        <el-tabs v-model="activeTab" @tab-click="handleTabClick">
+          <el-tab-pane label="按天统计" name="daily">
+            <el-form :inline="true" class="demo-form-inline">
+              <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="日期">
+                <el-date-picker
+                  v-model="queryParams.dateTime"
+                  type="date"
+                  placeholder="选择日期"
+                  value-format="yyyy-MM-dd"
+                  @change="handleDailyDateChange"
+                ></el-date-picker>
+              </el-form-item>
+
+
+
+              <el-form-item>
+                <el-button type="primary" @click="searchDaily">查询</el-button>
+                <el-button @click="resetDaily">重置</el-button>
+                <el-button type="success" @click="exportDaily">导出</el-button>
+              </el-form-item>
+            </el-form>
+
+            <el-table
+              v-loading="dailyLoading"
+              border
+              :data="companyList"
+              style="width: 100%"
+            >
+              <el-table-column prop="dateTime" label="日期" width="180" />
+              <el-table-column prop="companyName" label="公司名称" />
+              <el-table-column prop="nickName" label="销售名称" />
+              <el-table-column prop="totalAmount" label="红包金额" />
+            </el-table>
+
+            <div class="chart-container" style="margin-top: 20px; height: 400px">
+              <div ref="dailyChart" style="height: 100%; width: 100%"></div>
+            </div>
+          </el-tab-pane>
+
+          <el-tab-pane label="按月统计" name="monthly">
+            <el-form :inline="true" class="demo-form-inline">
+              <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="月份">
+                <el-date-picker
+                  v-model="queryParams.dateTime"
+                  type="month"
+                  placeholder="选择月份"
+                  value-format="yyyy-MM"
+                  @change="handleMonthlyDateChange"
+                ></el-date-picker>
+              </el-form-item>
+
+              <el-form-item>
+                <el-button type="primary" @click="searchMonthly">查询</el-button>
+                <el-button @click="resetMonthly">重置</el-button>
+                <el-button type="success" @click="exportMonthly">导出</el-button>
+              </el-form-item>
+            </el-form>
+
+            <el-table
+              v-loading="monthlyLoading"
+              border
+              :data="companyList"
+              style="width: 100%"
+            >
+              <el-table-column prop="dateTime" label="月份" width="180" />
+              <el-table-column prop="companyName" label="公司名称" />
+              <el-table-column prop="nickName" label="销售名称" />
+              <el-table-column prop="totalAmount" label="红包金额" />
+            </el-table>
+
+            <div class="chart-container" style="margin-top: 20px; height: 400px">
+              <div ref="monthlyChart" style="height: 100%; width: 100%"></div>
+            </div>
+          </el-tab-pane>
+        </el-tabs>
+      </el-card>
+    </div>
+  </template>
+
+  <script>
+  import { redPackageTotal,exportRedPackageByTime} from "@/api/his/redPackage";
+  import { getCompanyList } from "@/api/company/company";
+
+  export default {
+    name: 'redPackageStatic',
+    data() {
+      const today = this.parseTime(new Date(), '{y}-{m}-{d}')
+      const yesterday = this.parseTime(new Date(new Date().getTime() - 24 * 60 * 60 * 1000), '{y}-{m}-{d}')
+      return {
+        companys:[],
+        queryParams: {
+          companyId: null,
+          dateTime: yesterday,
+        },
+        // 激活的标签页
+        activeTab: 'daily',
+        // 按天统计数据
+        dailyLoading: false,
+        // 按月统计数据
+        monthlyLoading: false,
+        companyList: [],
+      }
+    },
+    created() {
+      getCompanyList().then(response => {
+        this.companys = response.data;
+      });
+      // 获取当天数据
+      this.getList()
+    },
+    methods: {
+      // 标签页切换
+      handleTabClick(tab) {
+        if (tab.name === 'daily') {
+          this.queryParams.dateTime = this.parseTime(new Date(new Date().getTime() - 24 * 60 * 60 * 1000), '{y}-{m}-{d}')
+          this.getList()
+        } else if (tab.name === 'monthly') {
+          this.queryParams.dateTime = this.parseTime(new Date(), '{y}-{m}')// 默认当月
+          this.getMonthlyList()
+        }
+      },
+
+      // 按天统计相关方法
+      handleDailyDateChange(val) {
+        if (val) {
+          this.queryParams.dateTime = val
+        }
+      },
+      searchDaily() {
+        this.getList()
+      },
+      resetDaily() {
+        const today = this.parseTime(new Date(), '{y}-{m}-{d}')
+        const yesterday = this.parseTime(new Date(new Date().getTime() - 24 * 60 * 60 * 1000), '{y}-{m}-{d}')
+        this.queryParams.dateTime = yesterday
+        this.getList()
+      },
+
+      // 导出
+      exportDaily() {
+        this.$confirm("是否确认导出数据项?", "警告", {
+            confirmButtonText: "确定",
+            cancelButtonText: "取消",
+            type: "warning",
+        })
+            .then(() => {
+            this.exportLoading = true;
+            return exportRedPackageByTime(this.queryParams);
+            })
+            .then((response) => {
+            this.download(response.msg);
+            this.exportLoading = false;
+            })
+            .catch(() => { });
+      },
+      getList() {
+        this.dailyLoading = true
+        redPackageTotal(this.queryParams).then(response => {
+          // 处理返回数据,添加日期字段用于表格和图表显示
+          this.companyList = response.rows;
+          this.dailyLoading = false;
+        }).catch(() => {
+          this.dailyLoading = false;
+        });
+      },
+
+      // 按月统计相关方法
+      handleMonthlyDateChange(val) {
+        if (val) {
+          this.queryParams.dateTime = val
+        }
+      },
+      searchMonthly() {
+        this.getMonthlyList()
+      },
+      resetMonthly() {
+        this.queryParams.dateTime = this.parseTime(new Date(), '{y}-{m}')
+        this.getMonthlyList()
+      },
+      exportMonthly() {
+        this.$confirm("是否确认导出数据项?", "警告", {
+            confirmButtonText: "确定",
+            cancelButtonText: "取消",
+            type: "warning",
+        })
+            .then(() => {
+            this.exportLoading = true;
+            return exportRedPackageByTime(this.queryParams);
+            })
+            .then((response) => {
+            this.download(response.msg);
+            this.exportLoading = false;
+            })
+            .catch(() => { });
+      },
+      getMonthlyList() {
+        this.monthlyLoading = true
+        redPackageTotal(this.queryParams).then(response => {
+          // 处理返回数据,添加月份字段用于表格和图表显示
+          this.companyList = response.rows;
+          this.monthlyLoading = false;
+        }).catch(() => {
+          this.monthlyLoading = false;
+          this.monthlyTotal = 0;
+        });
+      },
+
+      // 日期格式化
+      parseTime(time, pattern) {
+        let date;
+        if (arguments.length === 0 || !time) {
+          return null
+        }
+        const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
+
+        if (typeof time === 'object') {
+          date = time
+        } else {
+          if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
+            time = parseInt(time)
+          } else if (typeof time === 'string') {
+            time = time.replace(new RegExp(/-/gm), '/')
+          }
+          if ((typeof time === 'number') && (time.toString().length === 10)) {
+            time = time * 1000
+          }
+          date = new Date(time)
+        }
+        const formatObj = {
+          y: date.getFullYear(),
+          m: date.getMonth() + 1,
+          d: date.getDate(),
+          h: date.getHours(),
+          i: date.getMinutes(),
+          s: date.getSeconds(),
+          a: date.getDay()
+        }
+        const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+          let value = formatObj[key]
+          // Note: getDay() returns 0 on Sunday
+          if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
+          if (result.length > 0 && value < 10) {
+            value = '0' + value
+          }
+          return value || 0
+        })
+        return time_str
+      }
+    }
+  }
+  </script>
+
+  <style scoped>
+  .chart-container {
+    position: relative;
+    padding: 20px 0;
+  }
+  </style>

+ 43 - 0
src/views/his/company/index.vue

@@ -308,6 +308,38 @@
             :value="item.appId"
           />
         </el-select>
+      </el-form-item>
+      <el-form-item label="主要小程序" prop="miniAppMaster" >
+        <el-select
+          v-model="form.miniAppMaster"
+          multiple
+          :multiple-limit="1"
+          placeholder="请选择"
+          clearable
+          size="small">
+          <el-option
+            v-for="item in miniAppList"
+            :key="item.appId"
+            :label="item.appName"
+            :value="item.appId"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="备用小程序" prop="miniAppServer" >
+        <el-select
+          v-model="form.miniAppServer"
+          multiple
+          :multiple-limit="1"
+          placeholder="请选择"
+          clearable
+          size="small">
+          <el-option
+            v-for="item in miniAppList"
+            :key="item.appId"
+            :label="item.appName"
+            :value="item.appId"
+          />
+        </el-select>
       </el-form-item>
          <el-form-item label="备注" prop="remark">
           <el-input v-model="form.remark" type="textarea"  :rows="2" placeholder="请输入备注" />
@@ -436,6 +468,8 @@ export default {
         restartTime: '13:10',
         courseMaAppId: null,
         courseMiniAppId: null,
+        miniAppMaster: [],
+        miniAppList: [],
       },
       // 表单校验
      rules: {
@@ -523,6 +557,13 @@ export default {
     cateList().then((response) => {
       this.cateList = response.rows;
     });
+    // getConfigByKey("courseMa.config").then(response => {
+    //   if(response.data && response.data.configValue) {
+    //     this.miniAppList = JSON.parse(response.data.configValue);
+    //   } else {
+    //     this.miniAppList = [];
+    //   }
+    // });
 	docList().then(response => {
 	    this.doctor=response.rows;
 	});
@@ -607,6 +648,8 @@ export default {
         remark: null,
         linkName: null,
         limitUserCount: null,
+        miniAppMaster: [],
+        miniAppList: [],
         isDel: null,
         courseMaAppId: null,
         courseMiniAppId: null,

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

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

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

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

+ 42 - 2
src/views/his/package/index.vue

@@ -81,6 +81,16 @@
           />
         </el-select>
       </el-form-item>
+      <el-form-item label="节气" prop="solarTerm" >
+        <el-select v-model="queryParams.solarTerm" placeholder="请选择">
+          <el-option
+            v-for="dict in solarTermOptions"
+            :key="dict.value"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
+          />
+        </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>
@@ -171,6 +181,11 @@
           <dict-tag :options="orOptions" :value="scope.row.isShow"/>
         </template>
       </el-table-column>
+      <el-table-column label="节气" align="center" prop="solarTerm" width="75px">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.solarTerm">{{getSolarTermLabel(scope.row.solarTerm)}}</el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="创建时间" align="center" prop="createTime" width="180px"/>
       <el-table-column label="更改时间" align="center" prop="updateTime" width="180px"/>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="180px">
@@ -503,6 +518,16 @@
             </el-option>
           </el-select>
         </el-form-item>
+        <el-form-item label="节气" prop="solarTerm">
+          <el-select v-model="form.solarTerm" placeholder="请选择节气" clearable size="small">
+            <el-option
+              v-for="dict in solarTermOptions"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
         <el-form-item label="随访模板" prop="followTempId">
           <el-select v-model="form.followTempId" placeholder="请选择模板" clearable size="small">
                   <el-option
@@ -595,6 +620,7 @@ import { getToken } from "@/utils/auth";
 import Material from '@/components/Material'
 import ImageUpload from '@/components/ImageUpload'
 import { Loading } from 'element-ui';
+import { getOptions } from '@/api/his/packageSolarTerm'
 export default {
   name: "Package",
   components: { packageDetails,Editor,productAttrValueSelect,Material },
@@ -691,6 +717,7 @@ export default {
       // 是否删除字典
       isDelOptions: [],
       packageSubTypeOptions:[],
+      solarTermOptions: [],
       photoArr:[],
       // 查询参数
       queryParams: {
@@ -700,7 +727,8 @@ export default {
         sort: null,
         productJson: null,
         status: null,
-        isDel: null
+        isDel: null,
+        solarTerm: null,
       },
 
       // 表单参数
@@ -779,11 +807,23 @@ export default {
       this.privateTypeOptions = response.data.privateType;
     });
     this.getTemp();
+    this.getSolarTermOptions()
     listStore().then(response => {
       this.storeOPtions = response.rows;
     });
   },
   methods: {
+    getSolarTermLabel(solarTerm) {
+      if (solarTerm) {
+        return this.solarTermOptions.find(item => item.value === solarTerm)?.label || '-'
+      }
+      return '-'
+    },
+    getSolarTermOptions() {
+      getOptions().then(response => {
+        this.solarTermOptions = response.data;
+      })
+    },
     handleUpdateMore(row){
       this.reset1();
       this.title = "批量修改套餐状态";
@@ -1259,7 +1299,7 @@ export default {
      color: #8c939d;
      width: 150px;
      height: 150px;
-     line-height: 150px;
+     line-height: 150px !important;
      text-align: center;
    }
 </style>

+ 336 - 0
src/views/his/packageSolarTerm/index.vue

@@ -0,0 +1,336 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <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>
+        <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="['his:packageSolarTerm: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="['his:packageSolarTerm: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="['his:packageSolarTerm:remove']"
+        >删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table height="600" v-loading="loading" border :data="solarTermList" @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="name" />
+      <el-table-column label="开始时间" align="center" prop="startTime" width="180"/>
+      <el-table-column label="结束时间" align="center" prop="endTime" width="180"/>
+      <el-table-column label="节气简介" align="center" prop="desc" width="300">
+        <template slot-scope="scope">
+          <el-tooltip class="item" effect="dark" :content="scope.row.desc" placement="bottom-start">
+            <div style="overflow: hidden;white-space: nowrap;text-overflow: ellipsis;">
+              {{ scope.row.desc }}
+            </div>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+      <el-table-column label="排序" align="center" prop="sort"/>
+      <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" width="180"/>
+      <el-table-column label="更改时间" align="center" prop="updateTime" 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="['his:packageSolarTerm:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['his:packageSolarTerm: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="addOrUpdateDialog.title" :visible.sync="addOrUpdateDialog.open" width="1000px" append-to-body>
+      <el-form ref="form" :model="addOrUpdateDialog.form" :rules="addOrUpdateDialog.rules" label-width="110px">
+        <el-form-item label="节气名称" prop="name">
+          <el-input v-model="addOrUpdateDialog.form.name" placeholder="请输入节气名称" />
+        </el-form-item>
+        <el-form-item label="开始时间" prop="startTime">
+          <el-date-picker
+            v-model="addOrUpdateDialog.form.startTime"
+            type="datetime"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            placeholder="选择开始时间">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="结束时间" prop="endTime">
+          <el-date-picker
+            v-model="addOrUpdateDialog.form.endTime"
+            type="datetime"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            placeholder="选择结束时间">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="节气简介" prop="desc">
+          <el-input
+            type="textarea"
+            :rows="5"
+            v-model="addOrUpdateDialog.form.desc"
+            placeholder="请输入节气简介" />
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input-number :min="0" v-model="addOrUpdateDialog.form.sort" placeholder="请输入排序" />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="addOrUpdateDialog.form.status" placeholder="请选择状态">
+            <el-option
+              v-for="dict in statusOptions"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+
+import {
+  addPackageSolarTerm,
+  delPackageSolarTerm,
+  getPackageSolarTerm,
+  listPackageSolarTerm,
+  updatePackageSolarTerm
+} from '@/api/his/packageSolarTerm'
+
+export default {
+  name: 'solarTerm',
+  data() {
+    return {
+      showSearch: true,
+      statusOptions: [],
+      multiple: true,
+      single: true,
+      solarTermIds: [],
+      loading: true,
+      queryParams: {
+        name: null,
+        status: null
+      },
+      solarTermList: [],
+      total: 0,
+      addOrUpdateDialog: {
+        open: false,
+        title: null,
+        form: {
+          id: null,
+          name: null,
+          startTime: null,
+          endTime: null,
+          desc: null,
+          sort: null,
+          status: "1",
+        },
+        rules: {
+          name: [
+            { required: true, message: '请输入节气名称', trigger: 'blur' }
+          ],
+          startTime: [
+            { required: true, message: '请输入开始时间', trigger: 'blur' }
+          ],
+          endTime: [
+            { required: true, message: '请输入结束时间', trigger: 'blur' }
+          ],
+          desc: []
+        }
+      }
+    }
+  },
+  created() {
+    this.getDicts("solar_Term_status").then(response => {
+      this.statusOptions = response.data;
+    });
+    this.getList();
+  },
+  methods: {
+    getList() {
+      this.loading = true;
+      listPackageSolarTerm(this.queryParams).then(response => {
+        const {code, rows, total, msg} = response
+        if (code === 200) {
+          this.solarTermList = rows
+          this.total = total
+          this.loading = false
+          return
+        }
+        this.msgError(msg)
+      });
+    },
+    handleAdd() {
+      this.reset()
+      this.addOrUpdateDialog.title = "添加节气";
+      this.addOrUpdateDialog.open = true;
+    },
+
+    handleUpdate(row) {
+      this.reset()
+      const id = row.id || this.solarTermIds
+      getPackageSolarTerm(id).then(response => {
+        this.addOrUpdateDialog.form = response.data;
+        this.addOrUpdateDialog.form.status = response.data.status?.toString();
+        this.addOrUpdateDialog.title = "修改节气";
+        this.addOrUpdateDialog.open = true;
+      })
+    },
+    handleDelete(row) {
+      const ids = row.id || this.solarTermIds;
+      this.$confirm('是否确认删除节气编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delPackageSolarTerm(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    handleSelectionChange(selection) {
+      this.solarTermIds = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (!valid) {
+          return
+        }
+
+        if (this.addOrUpdateDialog.form.id != null) {
+          updatePackageSolarTerm(this.addOrUpdateDialog.form).then(response => {
+            const {code,msg} = response
+            if (code !== 200) {
+              this.msgError(msg)
+              return
+            }
+            this.msgSuccess("修改成功");
+            this.addOrUpdateDialog.open = false;
+            this.getList();
+          });
+          return
+        }
+
+        addPackageSolarTerm(this.addOrUpdateDialog.form).then(response => {
+          const {code,msg} = response
+          if (code !== 200) {
+            this.msgError(msg)
+            return
+          }
+          this.msgSuccess("新增成功");
+          this.addOrUpdateDialog.open = false;
+          this.getList();
+        });
+      });
+    },
+    cancel() {
+      this.addOrUpdateDialog.open = false;
+      this.reset();
+    },
+    reset() {
+      this.addOrUpdateDialog.form = {
+        name: null,
+        startTime: null,
+        endTime: null,
+        desc: null,
+        sort: null,
+        status: "1",
+      };
+      this.resetForm("form");
+    },
+  },
+}
+</script>
+
+<style scoped>
+
+</style>

+ 91 - 21
src/views/his/storeOrder/order1.vue

@@ -310,7 +310,7 @@
       <el-form-item label="ERP账户" prop="erpAccount" v-if="SFDFopen">
         <el-select v-model="queryParams.erpAccount" placeholder="ERP账户" clearable size="small">
           <el-option
-            v-for="dict in erpAccountList"
+            v-for="dict in erpAccountQueryList"
             :key="dict"
             :label="dict"
             :value="dict"
@@ -364,6 +364,16 @@
             v-hasPermi="['his:storeOrder:exportDeliver']"
           >导入发货</el-button>
         </el-col>
+        <el-col :span="1.5">
+          <el-button
+            type="info"
+            plain
+            icon="el-icon-upload2"
+            size="mini"
+            @click="handleImportStatus"
+            v-hasPermi="['his:storeOrder:editImport']"
+          >导入订单状态</el-button>
+        </el-col>
         <el-col :span="1.5">
           <el-button
             type="info"
@@ -589,6 +599,34 @@
         <el-button @click="upload.open = false">取 消</el-button>
       </div>
     </el-dialog>
+
+    <el-dialog :title="uploadStatus.title" :visible.sync="uploadStatus.open" width="400px" append-to-body>
+        <el-upload
+          ref="uploadStatus"
+          :limit="1"
+          accept=".xlsx, .xls"
+          :headers="uploadStatus.headers"
+          :action="uploadStatus.url + '?updateSupport=' + upload.updateSupport"
+          :disabled="uploadStatus.isUploading"
+          :on-progress="handleFileUploadProgressOrder"
+          :on-success="handleFileSuccessOrder"
+          :auto-upload="false"
+          drag
+        >
+          <i class="el-icon-upload"></i>
+          <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+          <div class="el-upload__tip text-center" slot="tip">
+            <div class="el-upload__tip" slot="tip">
+            </div>
+            <span>仅允许导入xls、xlsx格式文件。</span>
+            <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importUpdateOrderTemplate">下载模板</el-link>
+          </div>
+        </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitOrderStatusFileForm">确 定</el-button>
+        <el-button @click="uploadStatus.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
     
     <!-- 设置推送手机对话框 -->
     <el-dialog :title="erpPhone.title" :visible.sync="erpPhone.open" width="600px" append-to-body>
@@ -721,7 +759,7 @@
 
 <script>
 import { listOrder, getOrder, delOrder, addOrder,importExpressTemplate, updateOrder, exportOrder,importTemplate,exportOrder2,queryErpPhone,
-  saveErpPhone,editErpPhone,batchCreateErpOrder,getErpAccount,batchSetErpOrder } from "@/api/his/storeOrder";
+  saveErpPhone,editErpPhone,batchCreateErpOrder,getErpAccount,batchSetErpOrder,importUpdateOrderTemplate } from "@/api/his/storeOrder";
 import storeOrderDetails from '../../components/his/storeOrderDetails.vue';
 import { getToken } from "@/utils/auth";
 import {listStore} from "@/api/his/storeProduct";
@@ -738,13 +776,6 @@ export default {
     // 监听deptId
     'deptId': 'currDeptChange'
   },
-  // computed: {
-  //   // 计算选中账户的详细信息
-  //   selectedAccountInfo() {
-  //     if (!this.erpAccountForm.selectedAccount) return null;
-  //     return this.erpAccountList.find(account => account.id === this.erpAccountForm.selectedAccount);
-  //   }
-  // },
   data() {
     return {
       // 新增排序相关数据
@@ -784,6 +815,7 @@ export default {
         submitting: false
       },
       erpAccountList: [], // ERP账户列表
+      erpAccountQueryList:[], // ERP账户查询条件列表
       erpAccountForm: {
         selectedAccount: null // 选中的账户ID
       },
@@ -837,6 +869,20 @@ export default {
         // 上传的地址
         url: process.env.VUE_APP_BASE_API + "/his/storeOrder/importData"
       },
+      uploadStatus: {
+        // 是否显示弹出层
+        open: false,
+        // 弹出层标题
+        title: "",
+        // 是否禁用上传
+        isUploading: false,
+        // 是否更新已经存在的用户数据
+        updateSupport: 0,
+        // 设置上传的请求头部
+        headers: { Authorization: "Bearer " + getToken() },
+        // 上传的地址
+        url: process.env.VUE_APP_BASE_API + "/his/storeOrder/importOrderStatusData"
+      },
       // 遮罩层
       loading: true,
       // 导出遮罩层
@@ -924,7 +970,7 @@ export default {
         isFirst:null,
 		    source:null,
         companyId:null,
-        companyIds:null
+        companyIds:null,
       },
       // 表单参数
       form: {},
@@ -1008,7 +1054,6 @@ export default {
           this.deliveryStatusOptions = response.data;
     });
     this.getErpAccountList();
-
   },
   methods: {
     // 新增排序处理方法
@@ -1243,32 +1288,33 @@ export default {
     // 设置erp账户
     showErpAccountSetDialog() {
       this.erpAccountDialog.open = true;
-      this.erpAccountDialog.loading = true;
       this.erpSettingType = 'set'
-      // this.getErpAccountList();
       this.calculateOrderSummary();
     },
     //显示ERP账户选择对话框
     showErpAccountDialog() {
       this.erpAccountDialog.open = true;
-      this.erpAccountDialog.loading = true;
       this.erpSettingType = 'push'
-      // this.getErpAccountList();
       this.calculateOrderSummary();
     },
     
     //获取ERP账户列表
     async getErpAccountList() {
-    // getErpAccountList() {  
       try {
         const response = await getErpAccount();
-        // const response = getErpAccount();
         if (response.code === 200) {
-          this.erpAccountList = response.data || [];
+          const list = response.data || [];
+          this.erpAccountList = [...list];
+          this.erpAccountQueryList = [...list, '未分拣'];
+          // 设置默认值:第一条真实账户
+          if (list.length && !this.queryParams.erpAccount) {
+              this.$set(this.queryParams, 'erpAccount', list[0]);
+          }
         } else {
           this.$message.error(response.msg || '获取ERP账户列表失败');
           this.erpAccountList = [];
         }
+        
       } catch (error) {
         console.error('获取ERP账户列表失败:', error);
         this.$message.error('获取ERP账户列表失败');
@@ -1663,6 +1709,10 @@ export default {
         handleFileUploadProgressExpress(event, file, fileList) {
           this.upload.isUploading = true;
         },
+        // 文件上传中处理
+        handleFileUploadProgressOrder(event, file, fileList) {
+          this.uploadStatus.isUploading = true;
+        },
         // 文件上传成功处理
         handleFileSuccessExpress(response, file, fileList) {
           this.importExpress.open = false;
@@ -1671,14 +1721,26 @@ export default {
           this.$alert(response.msg, "导入结果", { dangerouslyUseHTMLString: true });
           this.getList();
         },
+         // 文件上传成功处理
+         handleFileSuccessOrder(response, file, fileList) {
+          this.uploadStatus.open = false;
+          this.uploadStatus.isUploading = false;
+          this.$refs.uploadStatus.clearFiles();
+          this.$alert(response.msg, "导入结果", { dangerouslyUseHTMLString: true });
+          this.getList();
+        },
+        
         // 提交上传文件
         submitFileFormExpress() {
           this.$refs.importExpress.submit();
         },
 
-
-    /** 导入按钮操作 */
-    handleImport() {
+      handleImportStatus() {
+        this.uploadStatus.title = "导入";
+        this.uploadStatus.open = true;
+       },
+      /** 导入按钮操作 */
+      handleImport() {
         this.upload.title = "导入";
         this.upload.open = true;
        },
@@ -1689,6 +1751,11 @@ export default {
           this.download(response.msg);
         });
       },
+      importUpdateOrderTemplate(){
+        importUpdateOrderTemplate().then(response => {
+          this.download(response.msg);
+        });
+      },
       // 文件上传中处理
       handleFileUploadProgress(event, file, fileList) {
         this.upload.isUploading = true;
@@ -1705,6 +1772,9 @@ export default {
       submitFileForm() {
         this.$refs.upload.submit();
       },
+      submitOrderStatusFileForm(){
+        this.$refs.uploadStatus.submit();
+      },
     handledetails(row){
         this.show.open=true;
         setTimeout(() => {

+ 44 - 2
src/views/his/storeProduct/index.vue

@@ -344,6 +344,22 @@
            <el-form-item label="轮播图" prop="images">
              <Material v-model="photoArr" type="image" :num="10" :width="150" :height="150" />
            </el-form-item>
+            <el-form-item label="商品视频" prop="video">
+          <div>
+            <el-upload
+              ref="uploadVideo"
+              class="upload-demo"
+              :action="uploadUrl"
+              :on-success="handleSuccess"
+              :before-upload="beforeUpload"
+              :limit="1"
+              accept="video/*"
+            >
+              <el-button size="small" type="primary">点击上传视频</el-button>
+            </el-upload>
+            <video v-if="form.videoUrl" :src="form.videoUrl" controls style="max-width: 300px; max-height: 300px; margin-top: 10px"></video>
+          </div>
+        </el-form-item>
           <el-form-item label="规格" prop="specType">
              <el-radio-group v-model="form.specType" >
                <el-radio :label="0" class="radio">单规格</el-radio>
@@ -846,7 +862,8 @@ export default {
       isDrug: [
          { required: true, message: "不能为空", trigger: "blur" }
        ],
-      }
+      },
+      myloading:null
     };
   },
   created() {
@@ -873,6 +890,31 @@ export default {
     });
   },
   methods: {
+    //上传成功
+     handleSuccess(response, file) {
+      // 上传成功后的回调函数
+      this.myloading.close();
+      this.form.videoUrl = response.url;
+      this.$refs.uploadVideo.clearFiles();
+    },
+    beforeUpload(file) {
+      // 上传前的钩子函数,可以在这里对文件进行处理
+      // 返回 false 则取消上传
+
+      // 例如限制文件大小
+      const isLt2M = file.size / 1024 / 1024 < 200;
+      if (!isLt2M) {
+        this.$message.error('上传视频文件大小不能超过 200MB!');
+        return false;
+      }
+      this.myloading = this.$loading({
+        lock: true,
+        text: '上传中',
+        spinner: 'el-icon-loading',
+        background: 'rgba(0, 0, 0, 0.7)'
+      });
+
+    },
     handleUpdatePrice(){
       this.priceOpen=true;
     },
@@ -1061,6 +1103,7 @@ export default {
         doctorBrokerage:null,
         imgUrl: null,
         images: null,
+        videoUrl: null,
         productName: null,
         productIntroduce: null,
         keyword: null,
@@ -1261,7 +1304,6 @@ export default {
             return this.$message.warning('请点击生成规格!');
           }
 
-
           addOrEdit(this.form).then(response => {
             if (response.code === 200) {
               this.msgSuccess("操作成功");

+ 81 - 143
src/views/his/user/index.vue

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

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

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

+ 400 - 0
src/views/his/userOnlineState/index.vue

@@ -0,0 +1,400 @@
+<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="phone">
+        <el-input
+          v-model="queryParams.phone"
+          placeholder="请输入手机号码"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="所属公司" prop="companyName">
+        <el-input
+          v-model="queryParams.companyName"
+          placeholder="请输入所属公司"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="所属销售" prop="companyUserNickName">
+        <el-input
+          v-model="queryParams.companyUserName"
+          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="['store:userOnlineState: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="['store:userOnlineState: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="['store:userOnlineState: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="['store:userOnlineState:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="userOnlineStateList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="用户id" align="center" prop="userId" />
+      <el-table-column label="用户昵称" align="center" prop="nickname" />
+      <el-table-column label="用户头像" align="center" width="80">
+        <template slot-scope="scope">
+          <el-popover
+            placement="right"
+            title=""
+            trigger="hover"
+          >
+            <img slot="reference" :src="scope.row.avatar" width="50" >
+            <img :src="scope.row.avatar" style="max-width: 120px;">
+          </el-popover>
+        </template>
+      </el-table-column>
+      <el-table-column label="手机号码" align="center" prop="phone" />
+      <el-table-column label="注册时间" align="center" prop="createTime" />
+      <el-table-column label="用户状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <el-tag prop="status"  v-if="scope.row.status === 1" type="success">正常</el-tag>
+          <el-tag prop="status"  v-if="scope.row.status === 0" type="warning">禁用</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="所属公司" align="center" prop="companyName" />
+      <el-table-column label="所属销售" align="center" prop="companyUserName" />
+      <el-table-column label="上线状态" align="center" prop="onlineStatus">
+      <template slot-scope="scope">
+        <el-tag prop="onlineStatus"  v-if="scope.row.onlineStatus === 1" type="success">已上线</el-tag>
+        <el-tag prop="onlineStatus"  v-if="scope.row.onlineStatus === 2" type="primary">未上线</el-tag>
+      </template>
+      </el-table-column>
+<!--      <el-table-column label="上线时间" align="center" prop="onlineTime" width="180">-->
+<!--        <template slot-scope="scope">-->
+<!--          <span>{{ parseTime(scope.row.onlineTime, '{y}-{m}-{d}') }}</span>-->
+<!--        </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="lastWatchDate" width="180">-->
+<!--        <template slot-scope="scope">-->
+<!--          <span>{{ parseTime(scope.row.lastWatchDate, '{y}-{m}-{d}') }}</span>-->
+<!--        </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="['store:userOnlineState:edit']"-->
+<!--          >修改</el-button>-->
+<!--          <el-button-->
+<!--            size="mini"-->
+<!--            type="text"-->
+<!--            icon="el-icon-delete"-->
+<!--            @click="handleDelete(scope.row)"-->
+<!--            v-hasPermi="['store:userOnlineState: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"
+    />
+
+<!--    &lt;!&ndash; 添加或修改用户上线情况对话框 &ndash;&gt;-->
+<!--    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>-->
+<!--      <el-form ref="form" :model="form" :rules="rules" label-width="80px">-->
+<!--        <el-form-item label="用户昵称" prop="nickname">-->
+<!--          <el-input v-model="form.nickname" placeholder="请输入用户昵称" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="用户头像" prop="avatar">-->
+<!--          <el-input v-model="form.avatar" placeholder="请输入用户头像" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="微信小程序OPENID" prop="maOpenId">-->
+<!--          <el-input v-model="form.maOpenId" placeholder="请输入微信小程序OPENID" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="微信公众号OPENID" prop="mpOpenId">-->
+<!--          <el-input v-model="form.mpOpenId" placeholder="请输入微信公众号OPENID" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="关联ID" prop="unionId">-->
+<!--          <el-input v-model="form.unionId" placeholder="请输入关联ID" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="公司id" prop="companyId">-->
+<!--          <el-input v-model="form.companyId" placeholder="请输入公司id" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="销售id" prop="companyUserId">-->
+<!--          <el-input v-model="form.companyUserId" placeholder="请输入销售id" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="上线状态,1-已上线;2-未上线">-->
+<!--          <el-radio-group v-model="form.onlineStatus">-->
+<!--            <el-radio label="1">请选择字典生成</el-radio>-->
+<!--          </el-radio-group>-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="上线时间" prop="onlineTime">-->
+<!--          <el-date-picker clearable size="small"-->
+<!--            v-model="form.onlineTime"-->
+<!--            type="date"-->
+<!--            value-format="yyyy-MM-dd"-->
+<!--            placeholder="选择上线时间">-->
+<!--          </el-date-picker>-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="看课数量" prop="watchCourseCount">-->
+<!--          <el-input v-model="form.watchCourseCount" placeholder="请输入看课数量" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="参与营期数量" prop="partCourseCount">-->
+<!--          <el-input v-model="form.partCourseCount" placeholder="请输入参与营期数量" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="最后一次看课时间" prop="lastWatchDate">-->
+<!--          <el-date-picker clearable size="small"-->
+<!--            v-model="form.lastWatchDate"-->
+<!--            type="date"-->
+<!--            value-format="yyyy-MM-dd"-->
+<!--            placeholder="选择最后一次看课时间">-->
+<!--          </el-date-picker>-->
+<!--        </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 { listUserOnlineState, getUserOnlineState, delUserOnlineState, addUserOnlineState, updateUserOnlineState, exportUserOnlineState } from "@/api/his/userOnlineState";
+
+export default {
+  name: "UserOnlineState",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 用户上线情况表格数据
+      userOnlineStateList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        nickname: null,
+        avatar: null,
+        phone: null,
+        maOpenId: null,
+        mpOpenId: null,
+        unionId: null,
+        companyId: null,
+        companyUserId: null,
+        onlineStatus: null,
+        onlineTime: null,
+        watchCourseCount: null,
+        partCourseCount: null,
+        lastWatchDate: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询用户上线情况列表 */
+    getList() {
+      this.loading = true;
+      listUserOnlineState(this.queryParams).then(response => {
+        this.userOnlineStateList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        userId: null,
+        nickname: null,
+        avatar: null,
+        phone: null,
+        createTime: null,
+        maOpenId: null,
+        mpOpenId: null,
+        unionId: null,
+        companyId: null,
+        companyUserId: null,
+        onlineStatus: 0,
+        onlineTime: null,
+        watchCourseCount: null,
+        partCourseCount: null,
+        lastWatchDate: 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.userId)
+      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
+      getUserOnlineState(userId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改用户上线情况";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.userId != null) {
+            updateUserOnlineState(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addUserOnlineState(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const userIds = row.userId || this.ids;
+      this.$confirm('是否确认删除用户上线情况编号为"' + userIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delUserOnlineState(userIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有用户上线情况数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportUserOnlineState(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 63 - 38
src/views/statistics/section/inline.vue

@@ -120,6 +120,14 @@
           </span>
         </template>
       </el-table-column>
+
+      <el-table-column prop="trafficSum" label="流量数" align="center">
+        <template #default="scope">
+          {{ formatTraffic(scope.row.trafficSum) }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="regNum" label="注册数" align="center"/>
+
     </el-table>
 
     <pagination
@@ -214,6 +222,23 @@ export default {
     })
   },
   methods: {
+    formatTraffic(bytes) {
+      if (!bytes || bytes === 0) return '0 B';
+
+      const units = ['B', 'KB', 'MB', 'GB', 'TB'];
+      const k = 1024;
+
+      // 计算单位级别
+      const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+      // 确保不超出单位数组范围
+      const unitIndex = Math.min(i, units.length - 1);
+
+      // 计算值并保留2位小数
+      const value = bytes / Math.pow(k, unitIndex);
+
+      return `${value.toFixed(2)} ${units[unitIndex]}`;
+    },
     getSummaries(param) {
       const { columns, data } = param;
       const sums = [];
@@ -224,48 +249,48 @@ export default {
           return;
         }
 
-        const values = data.map(item => Number(item[column.property]));
-
-        if (['periodPersonNum','periodNum', 'sendNum', 'notRegisteredNum', 'registeredNum',
-        'interruptNum', 'completedNum', 'qwRepeatNum', 'userRepeatNum','blackNum','deletedNum',
-        'orderNum','orderMoneyTotal','redPackageMoneyTotal','callNum','receivePassNum','receiveNotNum',
-      'callTimeTotal','remindPendingNum','remindProcessedNum'].includes(column.property)) {
-          if (!values.every(value => isNaN(value))) {
-            sums[index] = values.reduce((prev, curr) => {
-              const value = Number(curr);
-              if (!isNaN(value)) {
-                return prev + curr;
-              } else {
-                return prev;
-              }
-            }, 0);
-          } else {
-            sums[index] = 'N/A';
-          }
-          if(['orderMoneyTotal','redPackageMoneyTotal'].includes(column.property)){
+        // 定义所有需要求和的列
+        const sumColumns = [
+          'periodPersonNum', 'periodNum', 'sendNum', 'notRegisteredNum', 'registeredNum',
+          'interruptNum', 'completedNum', 'qwRepeatNum', 'userRepeatNum', 'blackNum', 'deletedNum',
+          'orderNum', 'orderMoneyTotal', 'redPackageMoneyTotal', 'callNum', 'receivePassNum',
+          'receiveNotNum', 'callTimeTotal', 'remindPendingNum', 'remindProcessedNum',
+          // 增加其他所有需要计算的数值列
+          'deleteNum', 'lineNum', 'orderTotalNum'
+        ];
+
+        const rateColumns = ['regRate', 'finishedRate'];
+
+        if (sumColumns.includes(column.property)) {
+          const values = data.map(item => Number(item[column.property]) || 0);
+          sums[index] = values.reduce((prev, curr) => prev + curr, 0);
+
+          if(['orderMoneyTotal', 'redPackageMoneyTotal'].includes(column.property)){
             sums[index] = sums[index].toFixed(2);
           }
         }
-        else if (column.property === 'regRate') {
-          const totalRegistered = data.reduce((sum, item) => sum + (Number(item.registeredNum) || 0), 0);
-          const totalSendNum = data.reduce((sum, item) => sum + (Number(item.sendNum) || 0), 0);
-
-          if (totalSendNum > 0) {
-            const rate = (totalRegistered / totalSendNum * 100).toFixed(2);
-            sums[index] = `${rate}%`;
-          } else {
-            sums[index] = '0.00%';
+        else if (rateColumns.includes(column.property)) {
+          if (column.property === 'regRate') {
+            const totalRegistered = data.reduce((sum, item) => sum + (Number(item.registeredNum) || 0), 0);
+            const totalSendNum = data.reduce((sum, item) => sum + (Number(item.sendNum) || 0), 0);
+
+            if (totalSendNum > 0) {
+              const rate = (totalRegistered / totalSendNum * 100).toFixed(2);
+              sums[index] = `${rate}%`;
+            } else {
+              sums[index] = '0.00%';
+            }
           }
-        }
-        else if (column.property === 'finishedRate') {
-          const totalCompleted = data.reduce((sum, item) => sum + (Number(item.completedNum) || 0), 0);
-          const totalSendNum = data.reduce((sum, item) => sum + (Number(item.sendNum) || 0), 0);
-
-          if (totalSendNum > 0) {
-            const rate = (totalCompleted / totalSendNum * 100).toFixed(2);
-            sums[index] = `${rate}%`;
-          } else {
-            sums[index] = '0.00%';
+          else if (column.property === 'finishedRate') {
+            const totalCompleted = data.reduce((sum, item) => sum + (Number(item.completedNum) || 0), 0);
+            const totalSendNum = data.reduce((sum, item) => sum + (Number(item.sendNum) || 0), 0);
+
+            if (totalSendNum > 0) {
+              const rate = (totalCompleted / totalSendNum * 100).toFixed(2);
+              sums[index] = `${rate}%`;
+            } else {
+              sums[index] = '0.00%';
+            }
           }
         }
         else {

+ 60 - 103
src/views/system/config/config.vue

@@ -155,8 +155,8 @@
           </el-form>
       </el-tab-pane>
 
-      <el-tab-pane label="协议配置" name="his.agreementConfig" :rules="rules3">
-          <el-form ref="form3" :model="form3"  label-width="150px">
+      <el-tab-pane label="协议配置" name="his.agreementConfig" >
+          <el-form ref="form3" :model="form3"  label-width="150px" :rules="rules4">
             <el-form-item label="医生注册协议" prop="doctorRegister">
               <editor v-model="form3.doctorRegister" :min-height="292"/>
             </el-form-item>
@@ -327,6 +327,15 @@
               <el-input-number  v-model="form7.followRate" :min="0" :max="1000"    ></el-input-number>
             </el-tooltip>
           </el-form-item>
+          <el-form-item  label="是否需要根据物流信息更新订单完成状态" prop="followRate">
+            <el-switch
+              v-model="form7.isUpdateOrder"
+              active-color="#13ce66"
+              inactive-color="#ff4949"
+              :active-value=1
+              :inactive-value=0>
+            </el-switch>
+          </el-form-item>
          <div   class="footer">
             <el-button type="primary" @click="submitForm7">提  交</el-button>
           </div>
@@ -521,92 +530,7 @@
         </div>
     </el-tab-pane>
     <el-tab-pane label="积分配置" name="his.integral">
-        <el-form ref="form11" :model="form11" :rules="rules3" label-width="180px">
-          <el-row>
-            <el-col :span="12">
-              <el-form-item  label="新手任务积分" prop="integralNewTask">
-                <el-tooltip class="item" effect="dark" content="首次app内下单公域疗法,赠送多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralNewTask"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item  label="购买消费" prop="integralRatio">
-                <el-tooltip class="item" effect="dark" content="实付多少元得1积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralRatio"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-          </el-row>
-          <el-row>
-            <el-col :span="12">
-              <el-form-item  label="分享获得积分" prop="integralShare">
-                <el-tooltip class="item" effect="dark" content="分享获得多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralShare"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item  label="完成随访获得积分" prop="integralFollow">
-                <el-tooltip class="item" effect="dark" content="完成随访获得多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralFollow"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-          </el-row>
-
-          <el-row>
-            <el-col :span="12">
-              <el-form-item  label="观看课程获得积分" prop="integralCourse">
-                <el-tooltip class="item" effect="dark" content="每小节观看>=50%获得多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralCourse"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item  label="浏览商品获得积分" prop="integralProduct">
-                <el-tooltip class="item" effect="dark" content="浏览商品每30秒获得多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralProduct"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-          </el-row>
-          <el-row>
-            <el-col :span="12">
-              <el-form-item  label="短视频/直播 获得积分" prop="integralFirstVideo">
-                <el-tooltip class="item" effect="dark" content="首次浏览每10秒获得多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralFirstVideo"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item  label="短视频/直播 获得积分" prop="integralFinishVideo">
-                <el-tooltip class="item" effect="dark" content="每10秒获得最低多少积分" placement="top-end">
-                  <el-input-number  v-model="form11.integralFinishVideo"   ></el-input-number>
-                </el-tooltip>
-              </el-form-item>
-            </el-col>
-          </el-row>
-          <el-form-item  label="单日可获总积分" prop="integralByOneDay">
-            <el-tooltip class="item" effect="dark" content="每天最多可获得多少积分" placement="top-end">
-              <el-input-number  v-model="form11.integralByOneDay"   ></el-input-number>
-            </el-tooltip>
-          </el-form-item>
-          <el-form-item label="单日可获总积分类型" prop="integralTypeByOneDay">
-            <el-select v-model="form11.integralTypeByOneDay" multiple placeholder="请选择类型" filterable clearable size="small">
-              <el-option
-                v-for="dict in integralLogTypeOptions"
-                :key="dict.dictValue"
-                :label="dict.dictLabel"
-                :value="dict.dictValue"
-              />
-            </el-select>
-          </el-form-item>
-
-         <div   class="footer">
-            <el-button type="primary" @click="submitForm11">提  交</el-button>
-          </div>
-        </el-form>
+      <IntegralConfig/>
     </el-tab-pane>
     <el-tab-pane label="签到配置" name="his.sign">
     <el-table border :data="form12">
@@ -685,7 +609,7 @@
 
 
     <el-tab-pane label="系统配置" name="his.config">
-     <el-form ref="form11" :model="form13"  label-width="160px">
+     <el-form ref="form13" :model="form13"  label-width="160px">
       <el-form-item   label="腾讯云sdkAppId" prop="sdkAppId">
           <el-input   v-model="form13.sdkAppId"  label="请输入sdkAppId"></el-input>
       </el-form-item>
@@ -1194,6 +1118,14 @@
             inactive-color="#ff4949">
           </el-switch>
         </el-form-item>
+
+           <el-form-item label="是否允许销售公司金额位负数" prop="isAllowUserPause">
+             <el-switch
+               v-model="form18.isNegative"
+               active-color="#13ce66"
+               inactive-color="#ff4949">
+             </el-switch>
+           </el-form-item>
            <el-form-item label="可查看评论条数" v-if="false">
              <el-tooltip class="item" effect="dark" content="在评论区可以查看的历史评论条数,默认200" placement="top-end">
                <el-input-number  v-model="form18.viewCommentNum" :min="1"></el-input-number>
@@ -1361,6 +1293,12 @@
              </el-tooltip>
            </el-form-item>
 
+           <el-form-item label="超过多少天的客户没看课评E级" prop="levelDay">
+             <el-tooltip class="item" effect="dark" content="超过多少天的客户没看课评E级,且不生成sop执行记录了,不发了" placement="top-end">
+               <el-input-number v-model="form20.notStudyDays" :min="7" :max="365"></el-input-number>
+             </el-tooltip>
+           </el-form-item>
+
            <!-- A级范围 -->
            <el-form-item label="A级范围(%)" prop="aLevelMin">
              <el-tooltip class="item" effect="dark" content="A级客户范围最小值(%)" placement="top-end">
@@ -1497,14 +1435,15 @@ import {getAllFollowTempName } from "@/api/his/followTemp";
 import productAttrValueSelect from "../../components/his/productGiftValueSelect.vue";
 import productDeliveryGiftValueSelect from "../../components/his/productDeliveryGiftValueSelect.vue";
 import { Col } from "element-ui";
-import Editor from '@/components/Editor/wang';
 import companyMenuConfig from "./companyMenuConfig";
+import IntegralConfig from '@/views/system/config/integralConfig.vue'
 import {getCitys} from "@/api/store/city";
 export default {
   name: "Config",
   components: {
     companyMenuConfig,
-    Material,productAttrValueSelect,productDeliveryGiftValueSelect,Editor
+    Material,productAttrValueSelect,productDeliveryGiftValueSelect,
+    IntegralConfig
   },
   data() {
     return {
@@ -1546,8 +1485,6 @@ export default {
       form9:{
       },
       form10:[],
-      form11:{
-      },
       form12:[],
       form13:{
         dfAccounts: [] // 初始化代付管家账户数组
@@ -1567,6 +1504,7 @@ export default {
       },
       form20:{
         levelDay: 7,
+        notStudyDays:7,
         aLevelMin: 80,
         aLevelMax: 100,
         bLevelMin: 40,
@@ -1590,6 +1528,26 @@ export default {
       },
       rules3: {
       },
+      rules4: {
+        doctorRegister: [
+          { required: true, message: '请输入医生注册协议', trigger: 'blur' }
+        ],
+        doctorFiling: [
+          { required: true, message: '请输入医生多机构备案协议', trigger: 'blur' }
+        ],
+        userRegister: [
+          { required: true, message: '请输入用户协议', trigger: 'blur' }
+        ],
+        userPrivacy: [
+          { required: true, message: '请输入隐私协议', trigger: 'blur' }
+        ],
+        userHealth: [
+          { required: true, message: '请输入健康客服协议', trigger: 'blur' }
+        ],
+        vipService: [
+          { required: true, message: '请输入会员服务协议', trigger: 'blur' }
+        ]
+      },
       rulesDelay:[
                 {
                   validator: (rule, value, callback) => {
@@ -1809,7 +1767,14 @@ export default {
               });
           }
           if(key=="his.store"){
-              this.form7 =JSON.parse(response.data.configValue);
+              // this.form7 =JSON.parse(response.data.configValue);
+              const parsed = JSON.parse(response.data.configValue);
+              if (parsed.isUpdateOrder != null) {
+                parsed.isUpdateOrder = Number(parsed.isUpdateOrder);
+              } else {
+                parsed.isUpdateOrder = 1; // 默认值
+              }
+              this.form7 = parsed
           }
           if(key=="his.package"){
               this.form8 =JSON.parse(response.data.configValue);
@@ -1821,7 +1786,7 @@ export default {
               this.form10 =JSON.parse(response.data.configValue);
           }
           if(key=="his.integral"){
-              this.form11 =JSON.parse(response.data.configValue);
+            return
           }
           if(key=="his.sign"){
               this.form12 =JSON.parse(response.data.configValue);
@@ -1966,14 +1931,6 @@ export default {
        }
      });
   },
-  submitForm11(){
-    var param={configId:this.configId,configValue:JSON.stringify(this.form11)}
-    updateConfigByKey(param).then(response => {
-       if (response.code === 200) {
-         this.msgSuccess("修改成功");
-       }
-     });
-  },
   submitForm12(){
     var param={configId:this.configId,configValue:JSON.stringify(this.form12)}
     updateConfigByKey(param).then(response => {

+ 7 - 0
src/views/system/config/config2.vue

@@ -1176,6 +1176,12 @@
              </el-tooltip>
            </el-form-item>
 
+           <el-form-item label="超过多少天的客户没看课评E级" prop="levelDay">
+             <el-tooltip class="item" effect="dark" content="超过多少天的客户没看课评E级,且不生成sop执行记录了,不发了" placement="top-end">
+               <el-input-number v-model="form20.notStudyDays" :min="7" :max="365"></el-input-number>
+             </el-tooltip>
+           </el-form-item>
+
            <!-- A级范围 -->
            <el-form-item label="A级范围(%)" prop="aLevelMin">
              <el-tooltip class="item" effect="dark" content="A级客户范围最小值(%)" placement="top-end">
@@ -1361,6 +1367,7 @@ export default {
       },
       form20:{
         levelDay: 7,
+        notStudyDays:7,
         aLevelMin: 80,
         aLevelMax: 100,
         bLevelMin: 40,

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

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

+ 13 - 0
src/views/system/role/index.vue

@@ -189,6 +189,18 @@
             >{{dict.dictLabel}}</el-radio>
           </el-radio-group>
         </el-form-item>
+        <el-form-item label="是否可以查看手机全号" >
+          <span slot="label">
+            <el-tooltip content="是否可以查看手机全号" placement="top">
+              <i class="el-icon-question"></i>
+            </el-tooltip>
+            号码权限
+          </span>
+          <el-radio-group v-model="form.isCheckPhone">
+            <el-radio :label=0>否</el-radio>
+            <el-radio :label=1>是</el-radio>
+          </el-radio-group>
+        </el-form-item>
         <el-form-item label="菜单权限">
           <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
           <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
@@ -454,6 +466,7 @@ export default {
         roleKey: undefined,
         roleSort: 0,
         status: "0",
+        isCheckPhone:0,
         menuIds: [],
         deptIds: [],
         menuCheckStrictly: true,

+ 182 - 0
src/views/user/blacklist/index.vue

@@ -0,0 +1,182 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px" @submit.native.prevent>
+      <el-form-item label="关键词" prop="keyword">
+        <el-input
+          style="width:220px"
+          v-model="queryParams.keyword"
+          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-button-->
+<!--        size="mini"-->
+<!--        type="primary"-->
+<!--        style="margin-left: 5px"-->
+<!--        :disabled="ids.length === 0"-->
+<!--        @click="handleUpdateBatch"-->
+<!--        v-hasPermi="['store:user:enabledUsers']"-->
+<!--      >批量启用</el-button>-->
+<!--      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>-->
+<!--    </el-row>-->
+
+    <el-table  height="500" border v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="用户id" align="center" prop="userId" />
+      <el-table-column label="项目" align="center" prop="projectName" />
+      <el-table-column label="用户昵称" align="center" prop="nickname" />
+      <el-table-column label="用户头像" align="center" prop="avatar">
+        <template slot-scope="scope">
+          <el-popover trigger="hover" placement="top">
+            <el-image :src="scope.row.avatar" style="width: 200px;height: 200px"/>
+            <div slot="reference" class="name-wrapper">
+              <el-avatar shape="square" size="large" :src="scope.row.avatar"/>
+            </div>
+          </el-popover>
+        </template>
+      </el-table-column>
+      <el-table-column label="手机号码" align="center" prop="phone" width="160px"/>
+      <el-table-column label="注册时间" align="center" prop="createTime" width="160px"/>
+<!--      <el-table-column label="看课数量" align="center" prop="watchCourseCount" />-->
+<!--      <el-table-column label="缺课数量" align="center" prop="missCourseCount" />-->
+<!--      <el-table-column label="缺课状态" align="center" prop="missCourseStatus">-->
+<!--        <template slot-scope="scope">-->
+<!--          <el-tag effect="dark" type="danger"  v-if="scope.row.missCourseStatus === 1">已缺课</el-tag>-->
+<!--          <el-tag effect="dark" type="success" v-else>未缺课</el-tag>-->
+<!--        </template>-->
+<!--      </el-table-column>-->
+<!--      <el-table-column label="参与营期数量" align="center" prop="partCourseCount" width="100px"/>-->
+      <el-table-column label="最后一次看课时间" align="center" prop="lastWatchDate"  width="160px"/>
+      <el-table-column label="用户状态" align="center" prop="courseCountStatus">
+        <template slot-scope="scope">
+          <el-tag effect="dark" type="success" v-if="scope.row.courseCountStatus === 1">正常</el-tag>
+          <el-tag effect="dark" type="info"    v-else-if="scope.row.courseCountStatus === 2">停止</el-tag>
+          <el-tag effect="dark" type="danger"  v-else>未看</el-tag>
+        </template>
+      </el-table-column>
+<!--      <el-table-column label="停课天数" align="center" prop="stopWatchDays" />-->
+<!--      <el-table-column label="完播时间" align="center" prop="completeWatchDate" width="160px"/>-->
+      <el-table-column label="标签名称" align="center" prop="tag" />
+      <el-table-column label="所属销售名称" align="center" prop="companyUserNickName" width="120px"/>
+      <el-table-column label="备注" align="center" prop="remark" />
+<!--      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">-->
+<!--        <template slot-scope="scope">-->
+<!--          <el-button-->
+<!--            size="mini"-->
+<!--            type="text"-->
+<!--            @click="handleUpdate(scope.row)"-->
+<!--            v-hasPermi="['store:user:darkRoomList']"-->
+<!--            v-if="scope.row.isRepeatFans !== 1"-->
+<!--          >启用</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 { blacklist,enabledBlackUsers } from "@/api/his/user";
+
+export default {
+  name: "blacklist",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 用户列表数据
+      userList: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        keyword: null,
+        status: 2
+      },
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询客户列表 */
+    getList() {
+      this.loading = true;
+      blacklist(this.queryParams).then(response => {
+        this.userList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.userId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    // 启用
+    handleUpdate(row) {
+      enabledBlackUsers([row.userId]).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("取消禁用成功");
+          this.getList();
+        } else {
+          this.msgError(response.msg);
+        }
+      });
+    },
+    // 启用
+    handleUpdateBatch() {
+      enabledBlackUsers(this.ids).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("取消禁用成功");
+          this.getList();
+        } else {
+          this.msgError(response.msg);
+        }
+      });
+    }
+  }
+};
+</script>
+<style scoped>
+.app-container {
+  height: 87.5vh;
+}
+</style>

+ 35 - 33
src/views/user/darkRoom/index.vue

@@ -17,21 +17,22 @@
       </el-form-item>
     </el-form>
 
-    <el-row :gutter="10" class="mb8">
-      <el-button
-        size="mini"
-        type="primary"
-        style="margin-left: 5px"
-        :disabled="ids.length === 0"
-        @click="handleUpdateBatch"
-        v-hasPermi="['his:user:enabledUsers']"
-      >批量启用</el-button>
-      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
+<!--    <el-row :gutter="10" class="mb8">-->
+<!--      <el-button-->
+<!--        size="mini"-->
+<!--        type="primary"-->
+<!--        style="margin-left: 5px"-->
+<!--        :disabled="ids.length === 0"-->
+<!--        @click="handleUpdateBatch"-->
+<!--        v-hasPermi="['his:user:enabledUsers']"-->
+<!--      >批量启用</el-button>-->
+<!--      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>-->
+<!--    </el-row>-->
 
     <el-table  height="500" border v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="用户id" align="center" prop="userId" />
+      <el-table-column label="项目" align="center" prop="projectName" />
       <el-table-column label="用户昵称" align="center" prop="nickname" />
       <el-table-column label="用户头像" align="center" prop="avatar">
         <template slot-scope="scope">
@@ -43,16 +44,17 @@
           </el-popover>
         </template>
       </el-table-column>
+      <el-table-column label="手机号码" align="center" prop="phone" width="160px"/>
       <el-table-column label="注册时间" align="center" prop="createTime" width="160px"/>
-      <el-table-column label="看课数量" align="center" prop="watchCourseCount" />
-      <el-table-column label="缺课数量" align="center" prop="missCourseCount" />
-      <el-table-column label="缺课状态" align="center" prop="missCourseStatus">
-        <template slot-scope="scope">
-          <el-tag effect="dark" type="danger"  v-if="scope.row.missCourseStatus === 1">已缺课</el-tag>
-          <el-tag effect="dark" type="success" v-else>未缺课</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="参与营期数量" align="center" prop="partCourseCount" width="100px"/>
+<!--      <el-table-column label="看课数量" align="center" prop="watchCourseCount" />-->
+<!--      <el-table-column label="缺课数量" align="center" prop="missCourseCount" />-->
+<!--      <el-table-column label="缺课状态" align="center" prop="missCourseStatus">-->
+<!--        <template slot-scope="scope">-->
+<!--          <el-tag effect="dark" type="danger"  v-if="scope.row.missCourseStatus === 1">已缺课</el-tag>-->
+<!--          <el-tag effect="dark" type="success" v-else>未缺课</el-tag>-->
+<!--        </template>-->
+<!--      </el-table-column>-->
+<!--      <el-table-column label="参与营期数量" align="center" prop="partCourseCount" width="100px"/>-->
       <el-table-column label="最后一次看课时间" align="center" prop="lastWatchDate"  width="160px"/>
       <el-table-column label="用户状态" align="center" prop="courseCountStatus">
         <template slot-scope="scope">
@@ -61,21 +63,21 @@
           <el-tag effect="dark" type="danger"  v-else>未看</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="停课天数" align="center" prop="stopWatchDays" />
-      <el-table-column label="完播时间" align="center" prop="completeWatchDate" width="160px"/>
+<!--      <el-table-column label="停课天数" align="center" prop="stopWatchDays" />-->
+<!--      <el-table-column label="完播时间" align="center" prop="completeWatchDate" width="160px"/>-->
       <el-table-column label="标签名称" align="center" prop="tag" />
       <el-table-column label="所属销售名称" align="center" prop="companyUserNickName" width="120px"/>
       <el-table-column label="备注" align="center" prop="remark" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-        <template slot-scope="scope">
-          <el-button
-            size="mini"
-            type="text"
-            @click="handleUpdate(scope.row)"
-            v-hasPermi="['his:user:darkRoomList']"
-          >启用</el-button>
-        </template>
-      </el-table-column>
+<!--      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">-->
+<!--        <template slot-scope="scope">-->
+<!--          <el-button-->
+<!--            size="mini"-->
+<!--            type="text"-->
+<!--            @click="handleUpdate(scope.row)"-->
+<!--            v-hasPermi="['his:user:darkRoomList']"-->
+<!--          >启用</el-button>-->
+<!--        </template>-->
+<!--      </el-table-column>-->
     </el-table>
 
     <pagination
@@ -114,7 +116,7 @@ export default {
         pageNum: 1,
         pageSize: 10,
         keyword: null,
-        isBlack: true
+        status: 0
       },
     };
   },

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

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