xw 21 ساعت پیش
والد
کامیت
7976b64ba5
100فایلهای تغییر یافته به همراه8566 افزوده شده و 279 حذف شده
  1. 5 0
      .env.prod-ddgy
  2. 6 0
      .env.prod-fby
  3. 7 0
      .env.prod-fzbt
  4. 8 0
      .env.prod-gzzdy
  5. 6 0
      .env.prod-hcl
  6. 1 1
      .env.prod-heyantang
  7. 5 0
      .env.prod-hst
  8. 6 0
      .env.prod-hzyy
  9. 1 1
      .env.prod-jnmy
  10. 6 0
      .env.prod-jzzx
  11. 9 0
      .env.prod-knt
  12. 5 0
      .env.prod-kyt
  13. 5 0
      .env.prod-sczy
  14. 12 8
      .env.prod-shdn
  15. 44 0
      .env.prod-xcsw
  16. 11 1
      .env.prod-ylrz
  17. 46 0
      .env.prod-zlwh
  18. 2 4
      Dockerfile
  19. 3 0
      package.json
  20. 9 1
      src/api/company/companyDept.js
  21. 7 0
      src/api/company/companySmsTemp.js
  22. 12 0
      src/api/company/companyUser.js
  23. 17 0
      src/api/course/coursePlaySourceConfig.js
  24. 24 1
      src/api/course/courseRedPacketLog.js
  25. 8 0
      src/api/course/qw/courseWatchLog.js
  26. 28 1
      src/api/course/userCourseCategory.js
  27. 8 0
      src/api/course/userCoursePeriod.js
  28. 25 0
      src/api/course/userCourseVideo.js
  29. 14 0
      src/api/course/userWatchCourseStatistics.js
  30. 7 1
      src/api/crm/customer.js
  31. 53 0
      src/api/fastGpt/fastGptChatMsg.js
  32. 60 0
      src/api/fastGpt/fastGptChatMsgLogs.js
  33. 53 0
      src/api/fastGpt/fastGptChatSession.js
  34. 9 0
      src/api/fastGpt/fastGptRole.js
  35. 9 0
      src/api/fastGpt/fastgptPushTokenTotal.js
  36. 110 0
      src/api/his/aiWorkflow.js
  37. 15 1
      src/api/his/packageOrder.js
  38. 9 0
      src/api/his/storePayment.js
  39. 2 2
      src/api/his/vessel.js
  40. 34 1
      src/api/hisStore/storeOrder.js
  41. 9 0
      src/api/hisStore/storeOrderItem.js
  42. 2 2
      src/api/live/liveData.js
  43. 9 0
      src/api/live/liveMsg.js
  44. 16 0
      src/api/live/wxExpressTask.js
  45. 86 0
      src/api/qw/QwWorkTaskNew.js
  46. 53 0
      src/api/qw/autoTags.js
  47. 53 0
      src/api/qw/autoTagsLogs.js
  48. 31 0
      src/api/qw/friendWelcome.js
  49. 62 0
      src/api/qw/groupChat.js
  50. 93 0
      src/api/qw/groupMsg.js
  51. 71 0
      src/api/qw/groupMsgUser.js
  52. 74 0
      src/api/qw/group_chat_user.js
  53. 9 0
      src/api/qw/qwUser.js
  54. 34 0
      src/api/qw/qwUserVoiceLogTotal.js
  55. 15 0
      src/api/qw/tagGroup.js
  56. 156 3
      src/api/qw/user.js
  57. 29 0
      src/api/user/integral.js
  58. BIN
      src/assets/logo/xcsw.png
  59. BIN
      src/assets/logo/zlwh.png
  60. 1 2
      src/components/TreeSelect/index.vue
  61. 1050 0
      src/components/TreeSelect/indexOld.vue
  62. 13 0
      src/directive/select/elSelectLoadMore.js
  63. 8 0
      src/directive/select/index.js
  64. 2 1
      src/main.js
  65. 25 0
      src/router/index.js
  66. 1 0
      src/utils/cos.js
  67. 42 9
      src/views/company/companyMoneyLogs/index.vue
  68. 17 7
      src/views/company/companyUser/indexAll.vue
  69. 245 38
      src/views/components/course/userCourseCatalogDetails.vue
  70. 40 35
      src/views/components/his/integralOrderDetails.vue
  71. 18 4
      src/views/components/his/userDetails.vue
  72. 275 0
      src/views/components/his/userStoreOrderList.vue
  73. 60 4
      src/views/components/index/statisticsDashboard.vue
  74. 138 34
      src/views/course/coursePlaySourceConfig/index.vue
  75. 403 0
      src/views/course/courseRedPacketLogCount/index.vue
  76. 67 3
      src/views/course/courseUserStatistics/qw/statistics.vue
  77. 77 2
      src/views/course/courseWatchLog/qw/statistics.vue
  78. 16 1
      src/views/course/userCourse/index.vue
  79. 168 2
      src/views/course/userCourseCategory/index.vue
  80. 1 0
      src/views/course/userCoursePeriod/statistics.vue
  81. 80 9
      src/views/course/videoResource/index.vue
  82. 74 0
      src/views/crm/components/addRemark.vue
  83. 143 0
      src/views/crm/components/addTag.vue
  84. 41 13
      src/views/crm/externalContact/index.vue
  85. 294 0
      src/views/fastGpt/fastGptChatMsg/index.vue
  86. 200 0
      src/views/fastGpt/fastGptChatSession/fastGptChatMsgDetails.vue
  87. 480 0
      src/views/fastGpt/fastGptChatSession/index.vue
  88. 263 0
      src/views/fastGpt/fastGptPushTokenTotalDept/index.vue
  89. 239 0
      src/views/fastGpt/fastGptRole/fastGptRole.vue
  90. 15 0
      src/views/fastGpt/fastgptEventLogTotal/index.vue
  91. 1 1
      src/views/his/adv/index.vue
  92. 181 0
      src/views/his/aiWorkflow/design.scss
  93. 732 0
      src/views/his/aiWorkflow/design.vue
  94. 499 0
      src/views/his/aiWorkflow/index.vue
  95. 1 3
      src/views/his/company/index.vue
  96. 12 1
      src/views/his/companyRecharge/index.vue
  97. 3 3
      src/views/his/express/index.vue
  98. 424 79
      src/views/his/integralOrder/index.vue
  99. 527 0
      src/views/his/statistics/courseReport.vue
  100. 187 0
      src/views/his/storePayment/errorIndex.vue

+ 5 - 0
.env.prod-ddgy

@@ -27,6 +27,11 @@ VUE_APP_VIDEO_LINE_1 = https://ddgytcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://ddgyobs.ylrztop.com
 
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://ddgyvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = ddgy-2114522511
+
 # 开发环境配置
 ENV = 'development'
 

+ 6 - 0
.env.prod-fby

@@ -24,6 +24,12 @@ VUE_APP_COS_REGION = ap-chongqing
 VUE_APP_VIDEO_LINE_1 = https://fbytcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://fbyobs.ylrztop.com
+
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://fbyvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = fby-2114522511
+
 # 生产环境配置
 ENV = 'production'
 

+ 7 - 0
.env.prod-fzbt

@@ -26,6 +26,10 @@ VUE_APP_COS_REGION = ap-chongqing
 VUE_APP_VIDEO_LINE_1 = https://myhktcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://myhkobs.ylrztop.com
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://myhkvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = myhk-2114522511
 
 # 开发环境配置
 ENV = 'production'
@@ -38,3 +42,6 @@ VUE_APP_COURSE_DEFAULT = 2
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true
+
+# 项目
+VUE_APP_PROJECT = 'fzbt'

+ 8 - 0
.env.prod-gzzdy

@@ -27,6 +27,11 @@ VUE_APP_VIDEO_LINE_1 = https://gzzdytcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://gzzdyobs.ylrztop.com
 
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://gzzdyvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = gzzdy-2114522511
+
 # 开发环境配置
 ENV = 'development'
 
@@ -38,3 +43,6 @@ VUE_APP_COURSE_DEFAULT = 2
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true
+
+# 郑多燕
+VUE_APP_FS_USER_INFO = 'gzzdy'

+ 6 - 0
.env.prod-hcl

@@ -27,6 +27,12 @@ VUE_APP_VIDEO_LINE_1 = https://hcltcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://hclobs.ylrztop.com
 
+
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://hclvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = hcl-2114522511
+
 # 开发环境配置
 ENV = 'development'
 

+ 1 - 1
.env.prod-heyantang

@@ -32,7 +32,7 @@ VUE_APP_VIDEO_URL = https://cdhytvolcengine.ylrztop.com
 VUE_APP_HSY_SPACE = cdhyt-2114522511
 
 #直播解码路径
-LIVE_PATH = /live
+VUE_APP_LIVE_PATH = /live
 # 开发环境配置
 ENV = 'production'
 

+ 5 - 0
.env.prod-hst

@@ -27,6 +27,11 @@ VUE_APP_VIDEO_LINE_1 = https://hsttcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://hstobs.ylrztop.com
 
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://hstvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = hst-2114522
+
 # 开发环境配置
 ENV = 'development'
 

+ 6 - 0
.env.prod-hzyy

@@ -27,6 +27,12 @@ VUE_APP_VIDEO_LINE_1 = https://hzyytcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://hzyyobs.ylrztop.com
 
+
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://hzyyvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = hzyy-2114522511
+
 # 开发环境配置
 ENV = 'development'
 

+ 1 - 1
.env.prod-jnmy

@@ -28,7 +28,7 @@ VUE_APP_VIDEO_LINE_1 = https://jnmytcpv.ylrzcloud.com
 VUE_APP_VIDEO_LINE_2 = https://jnmyobs.ylrztop.com
 
 #火山云视频地址域名
-VUE_APP_VIDE0_URL = https://cdjnmyvolcengine.ylrztop.com
+VUE_APP_VIDEO_URL = https://cdjnmyvolcengine.ylrztop.com
 #火山云视频点播空间名
 VUE_APP_HSY_SPACE = cdjnmy-2114522511
 

+ 6 - 0
.env.prod-jzzx

@@ -27,6 +27,12 @@ VUE_APP_VIDEO_LINE_1 = https://jzzxtcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://jzzxobs.ylrztop.com
 
+
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://jzzxvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = jzzx-2114522511
+
 # 开发环境配置
 ENV = 'development'
 

+ 9 - 0
.env.prod-knt

@@ -26,9 +26,16 @@ VUE_APP_COS_REGION = ap-chongqing
 VUE_APP_VIDEO_LINE_1 = https://jnmytcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://jnmyobs.ylrztop.com
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://cdjnmyvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = cdjnmy-2114522511
 # 生产环境配置
 ENV = 'production'
 
+#直播解码路径
+LIVE_PATH = /live
+
 #FS管理系统/生产环境
 VUE_APP_BASE_API = '/prod-api'
 
@@ -37,3 +44,5 @@ VUE_APP_COURSE_DEFAULT = 1
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true
+
+VUE_APP_LIVE_WS_URL = wss://liveapp.kangniantangyiyao.top/ws

+ 5 - 0
.env.prod-kyt

@@ -27,6 +27,11 @@ VUE_APP_VIDEO_LINE_1 = https://kyttcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://kytobs.ylrztop.com
 
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://kytvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = kyt-2114522511
+
 # 开发环境配置
 ENV = 'development'
 

+ 5 - 0
.env.prod-sczy

@@ -27,6 +27,11 @@ VUE_APP_VIDEO_LINE_1 = https://sczycpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://sczyobs.ylrztop.com
 
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://sczytvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = sczy-2114522511
+
 # 开发环境配置
 ENV = 'development'
 

+ 12 - 8
.env.prod-shdn

@@ -1,11 +1,11 @@
 # 页面标题
-VUE_APP_TITLE =互联网医院管理系统
+VUE_APP_TITLE =德宁健康管理系统
 # 首页菜单标题
-VUE_APP_TITLE_INDEX =宽益堂管理系统
+VUE_APP_TITLE_INDEX =德宁健康管理系统
 # 公司名称
-VUE_APP_COMPANY_NAME =重庆云联融智科技有限公司
+VUE_APP_COMPANY_NAME =淄博德宁健康科技有限责任公司
 # ICP备案号
-VUE_APP_ICP_RECORD =渝ICP备2024031984号-1
+VUE_APP_ICP_RECORD =鲁ICP备2024100689号-2
 # ICP网站访问地址
 VUE_APP_ICP_URL =https://beian.miit.gov.cn
 # 网站LOG
@@ -17,15 +17,19 @@ VUE_APP_OBS_SECRET_ACCESS_KEY = sbyeNJLbcYmH6copxeFP9pAoksM4NIT9Zw4x0SRX
 # 存储桶配置
 VUE_APP_OBS_SERVER = https://obs.cn-north-4.myhuaweicloud.com
 # 存储桶配置
-VUE_APP_OBS_BUCKET = kyt-hw079058881
+VUE_APP_OBS_BUCKET = shdn-20251226
 # 存储桶配置
-VUE_APP_COS_BUCKET = kyt-1323137866
+VUE_APP_COS_BUCKET = shdn-1323137866
 # 存储桶配置
 VUE_APP_COS_REGION = ap-chongqing
 # 线路一地址
-VUE_APP_VIDEO_LINE_1 = https://kyttcpv.ylrzcloud.com
+VUE_APP_VIDEO_LINE_1 = https://shdn.ylrzcloud.com
 # 线路二地址
-VUE_APP_VIDEO_LINE_2 = https://kytobs.ylrztop.com
+VUE_APP_VIDEO_LINE_2 = https://shdnjbvolcengine.ylrztop.com
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://shdnjbvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = shdn-20251226
 
 # 开发环境配置
 ENV = 'development'

+ 44 - 0
.env.prod-xcsw

@@ -0,0 +1,44 @@
+# 页面标题
+VUE_APP_TITLE =星辰生物管理系统
+# 首页菜单标题
+VUE_APP_TITLE_INDEX =星辰生物管理系统
+# 公司名称
+VUE_APP_COMPANY_NAME =福州市星海星辰生物科技有限公司
+# ICP备案号
+VUE_APP_ICP_RECORD = 闽ICP备2025092516号-13
+# ICP网站访问地址
+VUE_APP_ICP_URL =https://beian.miit.gov.cn
+# 网站LOG
+VUE_APP_LOG_URL =@/assets/logo/xcsw.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 = xcsw-2114522511
+# 存储桶配置
+VUE_APP_COS_BUCKET = xcsw-1323137866
+# 存储桶配置
+VUE_APP_COS_REGION = ap-chongqing
+# 线路一地址
+VUE_APP_VIDEO_LINE_1 = https://xcswtcpv.ylrzcloud.com
+# 线路二地址
+VUE_APP_VIDEO_LINE_2 = https://xcswvolcengine.ylrztop.com
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://xcswvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = xcsw-2114522511
+
+# 开发环境配置
+ENV = 'development'
+
+# FS管理系统/开发环境
+VUE_APP_BASE_API = '/prod-api'
+
+#默认 1、会员 2、企微
+VUE_APP_COURSE_DEFAULT = 2
+
+# 路由懒加载
+VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 11 - 1
.env.prod-ylrz

@@ -26,7 +26,14 @@ VUE_APP_COS_REGION = ap-chongqing
 # 线路一地址
 VUE_APP_VIDEO_LINE_1 = https://ylrztcpv.ylrzcloud.com
 # 线路二地址
-VUE_APP_VIDEO_LINE_2 = https://myhkkobs.ylrztop.com
+VUE_APP_VIDEO_LINE_2 = https://cdhytobs.ylrztop.com
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://ylrzvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = ylrz-2114522511
+
+#直播解码路径
+VUE_APP_LIVE_PATH = /live
 
 # 开发环境配置
 ENV = 'production'
@@ -44,3 +51,6 @@ VUE_CLI_BABEL_TRANSPILE_MODULES = true
 VUE_APP_PATIENT_INFO = '客户信息'
 # 添加病人
 VUE_APP_ADD_PATIENT = '添加信息'
+
+
+VUE_APP_LIVE_WS_URL = wss://websocket.scrm.ylrzcloud.com/ws

+ 46 - 0
.env.prod-zlwh

@@ -0,0 +1,46 @@
+# 页面标题
+VUE_APP_TITLE =泽林文化泽林管理系统
+# 首页菜单标题
+VUE_APP_TITLE_INDEX 泽林文化管理系统
+# 公司名称
+VUE_APP_COMPANY_NAME =重庆云联融智科技有限公司
+# ICP备案号
+VUE_APP_ICP_RECORD =渝ICP备2024031984号-1
+# ICP网站访问地址
+VUE_APP_ICP_URL =https://beian.miit.gov.cn
+# 网站LOG
+VUE_APP_LOG_URL =@/assets/logo/zlwh.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 = zlwh-1323137866
+# 存储桶配置
+VUE_APP_COS_BUCKET = zlwh-1323137866
+# 存储桶配置
+VUE_APP_COS_REGION = ap-chongqing
+# 线路一地址
+VUE_APP_VIDEO_LINE_1 = https://zlwh.ylrzcloud.com
+# 线路二地址
+VUE_APP_VIDEO_LINE_2 = https://kytobs.ylrztop.com
+
+# 开发环境配置
+ENV = 'development'
+
+# FS管理系统/开发环境
+VUE_APP_BASE_API = '/prod-api'
+
+#默认 1、会员 2、企微
+VUE_APP_COURSE_DEFAULT = 2
+
+# 路由懒加载
+VUE_CLI_BABEL_TRANSPILE_MODULES = true
+
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://zlwhvolcengine.ylrztop.com
+
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = zlwh-2114522511

+ 2 - 4
Dockerfile

@@ -1,7 +1,5 @@
-
-
 #基于官方 NGINX 镜像部署(精简镜像)
-FROM nginx:alpine
+FROM swr.cn-east-2.myhuaweicloud.com/library/nginx:alpine
 
 # 复制本地打包好的 dist 目录到 NGINX 静态资源目录
 COPY ./dist /usr/share/nginx/html
@@ -13,4 +11,4 @@ COPY ./nginx.conf /etc/nginx/nginx.conf
 #EXPOSE 80
 
 # 启动 NGINX(前台运行,避免容器启动后退出)
-CMD ["nginx", "-g", "daemon off;"]
+CMD ["nginx", "-g", "daemon off;"]

+ 3 - 0
package.json

@@ -55,6 +55,8 @@
     "build:prod-hsyy": "vue-cli-service build --mode prod-hsyy",
     "build:prod-jnsyj": "vue-cli-service build --mode prod-jnsyj",
     "build:prod-shdn": "vue-cli-service build --mode prod-shdn",
+    "build:prod-xcsw": "vue-cli-service build --mode prod-xcsw",
+    "build:prod-zlwh": "vue-cli-service build --mode prod-zlwh",
     "preview": "node build/index.js --preview",
     "lint": "eslint --ext .js,.vue src"
   },
@@ -113,6 +115,7 @@
     "vue-cropper": "0.5.5",
     "vue-jsonp": "^2.0.0",
     "vue-meta": "^2.4.0",
+    "vue-mobile-calendar": "^3.3.0",
     "vue-router": "3.4.9",
     "vuedraggable": "^2.24.3",
     "vuex": "3.6.0",

+ 9 - 1
src/api/company/companyDept.js

@@ -58,4 +58,12 @@ export function treeselect(query) {
     method: 'get',
     params: query
   })
-}
+}
+
+export function treeselectByCompanyId(query) {
+  return request({
+    url: '/company/companyDept/treeselectByCompanyId/' + query,
+    method: 'get',
+    params: query
+  })
+}

+ 7 - 0
src/api/company/companySmsTemp.js

@@ -58,4 +58,11 @@ export function audit(data) {
     method: 'post',
     data: data
   })
+}
+export function getSmsTempList(query) {
+  return request({
+    url: '/company/companySmsTemp/getSmsTempList',
+    method: 'get',
+    params: query
+  })
 }

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

@@ -120,3 +120,15 @@ export function getCompanyUserListPage(query) {
     params: query
   })
 }
+export function getQwAllUserList(id,companyId) {
+  return request({
+    url: '/company/companyUser/getQwAllUserList/'+id+'/'+companyId,
+    method: 'get'
+  })
+}
+export function getCompanyListByCorpId(id) {
+  return request({
+    url: '/company/companyUser/getCompanyList/'+id,
+    method: 'get'
+  })
+}

+ 17 - 0
src/api/course/coursePlaySourceConfig.js

@@ -61,3 +61,20 @@ export function listAll(companyId) {
     params: {companyId}
   })
 }
+
+export function updateAgreementConfig(data) {
+  return request({
+    url: '/course/playSourceConfig/updateAgreementConfig',
+    method: 'post',
+    data: data
+  })
+}
+
+export function queryAgreementConfig(query) {
+  return request({
+    url: '/course/playSourceConfig/queryAgreementConfig',
+    method: 'get',
+    params: query
+  })
+}
+

+ 24 - 1
src/api/course/courseRedPacketLog.js

@@ -18,6 +18,14 @@ export function listCourseRedPacketLogPage(data) {
   })
 }
 
+export function getRedPacketLogCount(data) {
+  return request({
+    url: '/course/courseRedPacketLog/getRedPacketLogCount',
+    method: 'post',
+    data: data
+  })
+}
+
 // 查询短链课程看课记录详细
 export function getCourseRedPacketLog(logId) {
   return request({
@@ -27,7 +35,13 @@ export function getCourseRedPacketLog(logId) {
 }
 export function courseList() {
   return request({
-    url: '/course/courseRedPacketLog//courseList',
+    url: '/course/courseRedPacketLog/courseList',
+    method: 'get',
+  })
+}
+export function courseListByCompanyId(companyId) {
+  return request({
+    url: '/course/courseRedPacketLog/courseListByCompanyId/' + companyId,
     method: 'get',
   })
 }
@@ -104,3 +118,12 @@ export function getReadPackageTotal(query) {
     params: query
   })
 }
+
+// 导出短链课程看课记录
+export function exportCourseRedPacketLogCountExport(data) {
+  return request({
+    url: '/course/courseRedPacketLog/countExport',
+    method: 'post',
+    data: data
+  })
+}

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

@@ -142,6 +142,14 @@ export function getSignProjectName() {
     method: 'get'
   })
 }
+// 获取档期列表
+export function fetchScheduleList(query) {
+  return request({
+    url: '/qw/course/courseWatchLog/fetchScheduleList',
+    method: 'get',
+    params: query
+  })
+}
 
 export function getAppIdList(query) {
   return request({

+ 28 - 1
src/api/course/userCourseCategory.js

@@ -52,6 +52,15 @@ export function exportUserCourseCategory(query) {
   })
 }
 
+// 导出重粉
+export function exportFans(query) {
+  return request({
+    url: '/course/userCourseCategory/exportFans',
+    method: 'post',
+    data: query
+  })
+}
+
 // 查询分类父列表
 export function getAllCourseCategoryList() {
   return request({
@@ -73,4 +82,22 @@ export function getCateListByPid(pid) {
     url: '/course/userCourseCategory/getCateListByPid/' + pid,
     method: 'get'
   })
-}
+}
+
+
+// 下载模板
+export function importTemplate() {
+  return request({
+    url: '/course/userCourseCategory/importTemplate',
+    method: 'get'
+  })
+}
+
+export function exportFail(data) {
+  return request({
+    url: '/course/userCourseCategory/exportFail',
+    method: 'post',
+    data: data
+  })
+}
+

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

@@ -8,6 +8,14 @@ export function listPeriod(query) {
     params: query
   })
 }
+// 获取营期名称列表
+export function listPeriodLabel(query) {
+  return request({
+    url: '/course/period/listLabel/' + query,
+    method: 'get',
+    params: query
+  })
+}
 // 查询会员营期列表
 export function getDays(query) {
   return request({

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

@@ -151,4 +151,29 @@ export function getChooseCourseVideoList(query) {
   })
 }
 
+// 下架课堂视频
+export function batchDownUserCourseVideo(videoId) {
+  return request({
+    url: '/course/userCourseVideo/batchDown/' + videoId,
+    method: 'post'
+  })
+}
+
+// 上架课堂视频
+export function batchUpUserCourseVideo(videoId) {
+  return request({
+    url: '/course/userCourseVideo/batchUp/' + videoId,
+    method: 'post'
+  })
+}
+
+// 修改视频封面
+export function batchEditCover(data) {
+  return request({
+    url: '/course/userCourseVideo/batchEditCover',
+    method: 'post',
+    data: data
+  })
+}
+
 

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

@@ -69,3 +69,17 @@ export function exportUserWatchCourseStatisticsTotal(query) {
     params: query
   })
 }
+export function getCourseList() {
+  return request({
+    url: '/course/userCourse/selectCourseOptionsList',
+    method: 'get'
+  })
+}
+
+export function getVideosByCourse(id) {
+  return request({
+    url: '/course/userCourseVideo/getCourseVideoOptions',
+    method: 'get',
+    params: { courseId: id }
+  })
+}

+ 7 - 1
src/api/crm/customer.js

@@ -46,7 +46,13 @@ export function updateCustomerSource(data) {
   })
 }
 
-
+export function getCustomerDetails(query) {
+  return request({
+    url: '/crm/customer/getCustomerDetails/',
+    method: 'get',
+    params: query
+  })
+}
 
 // 删除客户
 export function delLineCustomer(customerId) {

+ 53 - 0
src/api/fastGpt/fastGptChatMsg.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询聊天记录列表
+export function listFastGptChatMsg(query) {
+  return request({
+    url: '/fastGpt/fastGptChatMsg/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询聊天记录详细
+export function getFastGptChatMsg(msgId) {
+  return request({
+    url: '/fastGpt/fastGptChatMsg/' + msgId,
+    method: 'get'
+  })
+}
+
+// 新增聊天记录
+export function addFastGptChatMsg(data) {
+  return request({
+    url: '/fastGpt/fastGptChatMsg',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改聊天记录
+export function updateFastGptChatMsg(data) {
+  return request({
+    url: '/fastGpt/fastGptChatMsg',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除聊天记录
+export function delFastGptChatMsg(msgId) {
+  return request({
+    url: '/fastGpt/fastGptChatMsg/' + msgId,
+    method: 'delete'
+  })
+}
+
+// 导出聊天记录
+export function exportFastGptChatMsg(query) {
+  return request({
+    url: '/fastGpt/fastGptChatMsg/export',
+    method: 'get',
+    params: query
+  })
+}

+ 60 - 0
src/api/fastGpt/fastGptChatMsgLogs.js

@@ -0,0 +1,60 @@
+import request from '@/utils/request'
+
+// 查询聊天记录日志列表
+export function listFastGptChatMsgLogs(query) {
+  return request({
+    url: '/fastGpt/fastGptChatMsgLogs/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function getChatMsgLogsList(query) {
+  return request({
+    url: '/fastGpt/fastGptChatMsgLogs/logsList',
+    method: 'get',
+    params: query
+  })
+}
+// 查询聊天记录日志详细
+export function getFastGptChatMsgLogs(logsId) {
+  return request({
+    url: '/fastGpt/fastGptChatMsgLogs/' + logsId,
+    method: 'get'
+  })
+}
+
+// 新增聊天记录日志
+export function addFastGptChatMsgLogs(data) {
+  return request({
+    url: '/fastGpt/fastGptChatMsgLogs',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改聊天记录日志
+export function updateFastGptChatMsgLogs(data) {
+  return request({
+    url: '/fastGpt/fastGptChatMsgLogs',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除聊天记录日志
+export function delFastGptChatMsgLogs(logsId) {
+  return request({
+    url: '/fastGpt/fastGptChatMsgLogs/' + logsId,
+    method: 'delete'
+  })
+}
+
+// 导出聊天记录日志
+export function exportFastGptChatMsgLogs(query) {
+  return request({
+    url: '/fastGpt/fastGptChatMsgLogs/export',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/fastGpt/fastGptChatSession.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询对话关系列表
+export function listFastGptChatSession(query) {
+  return request({
+    url: '/fastGpt/fastGptChatSession/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询对话关系详细
+export function getFastGptChatSession(sessionId) {
+  return request({
+    url: '/fastGpt/fastGptChatSession/' + sessionId,
+    method: 'get'
+  })
+}
+
+// 新增对话关系
+export function addFastGptChatSession(data) {
+  return request({
+    url: '/fastGpt/fastGptChatSession',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改对话关系
+export function updateFastGptChatSession(data) {
+  return request({
+    url: '/fastGpt/fastGptChatSession',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除对话关系
+export function delFastGptChatSession(sessionId) {
+  return request({
+    url: '/fastGpt/fastGptChatSession/' + sessionId,
+    method: 'delete'
+  })
+}
+
+// 导出对话关系
+export function exportFastGptChatSession(query) {
+  return request({
+    url: '/fastGpt/fastGptChatSession/export',
+    method: 'get',
+    params: query
+  })
+}

+ 9 - 0
src/api/fastGpt/fastGptRole.js

@@ -9,6 +9,15 @@ export function listFastGptRole(query) {
   })
 }
 
+// 查询应用列表
+export function newListFastGptRole(query) {
+  return request({
+    url: '/fastGpt/role/newList',
+    method: 'get',
+    params: query
+  })
+}
+
 export function getAllRoleList(query) {
   return request({
     url: '/fastGpt/role/getAllRoleList',

+ 9 - 0
src/api/fastGpt/fastgptPushTokenTotal.js

@@ -16,3 +16,12 @@ export function exportTokenTotal(data) {
     data: data
   })
 }
+
+
+export function getFastGptPushTokenDeptTotal(query) {
+  return request({
+    url: '/qw/qwPushCount/tokenListDept',
+    method: 'get',
+    params: query
+  })
+}

+ 110 - 0
src/api/his/aiWorkflow.js

@@ -0,0 +1,110 @@
+import request from '@/utils/request'
+
+// 查询AI工作流列表
+export function listWorkflow(query) {
+  return request({
+    url: '/his/aiWorkflow/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询AI工作流详细
+export function getWorkflow(workflowId) {
+  return request({
+    url: '/his/aiWorkflow/' + workflowId,
+    method: 'get'
+  })
+}
+
+// 新增AI工作流
+export function addWorkflow(data) {
+  return request({
+    url: '/his/aiWorkflow/save',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改AI工作流
+export function updateWorkflow(data) {
+  return request({
+    url: '/his/aiWorkflow/save',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改AI工作流状态
+export function updateWorkflowStatus(workflowId, status) {
+  return request({
+    url: '/his/aiWorkflow/status/' + workflowId + '/' + status,
+    method: 'put'
+  })
+}
+
+// 删除AI工作流
+export function delWorkflow(workflowId) {
+  return request({
+    url: '/his/aiWorkflow/' + workflowId,
+    method: 'delete'
+  })
+}
+
+// 复制AI工作流
+export function copyWorkflow(workflowId) {
+  return request({
+    url: '/his/aiWorkflow/copy/' + workflowId,
+    method: 'post'
+  })
+}
+
+// 获取节点类型列表
+export function getNodeTypes() {
+  return request({
+    url: '/his/aiWorkflow/nodeTypes',
+    method: 'get'
+  })
+}
+
+// 导出AI工作流
+export function exportWorkflow(query) {
+  return request({
+    url: '/his/aiWorkflow/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 根据workflowId获取绑定的销售列表
+export function getBindCompanyUserByWorkflowId(workflowId) {
+  return request({
+    url: '/his/aiWorkflow/getBindCompanyUserByWorkflowId/' + workflowId,
+    method: 'get'
+  })
+}
+
+// 获取销售列表
+export function listCompanyUser() {
+  return request({
+    url: '/his/aiWorkflow/listCompanyUser',
+    method: 'get'
+  })
+}
+
+// 检查销售是否已被绑定
+export function checkCompanyUserBeUsed(companyUserId) {
+  return request({
+    url: '/his/aiWorkflow/checkCompanyUserBeUsed/' + companyUserId,
+    method: 'get'
+  })
+}
+
+// 更新工作流绑定销售
+export function updateWorkflowBindCompanyUser(data) {
+  return request({
+    url: '/his/aiWorkflow/updateWorkflowBindCompanyUser',
+    method: 'post',
+    data: data
+  })
+}

+ 15 - 1
src/api/his/packageOrder.js

@@ -90,4 +90,18 @@ export function exportPackageOrder(query) {
     method: 'get',
     params: query
   })
-}
+}
+export function courseReport(query) {
+  return request({
+    url: '/company/statistics/courseReport',
+    method: 'get',
+    params: query
+  })
+}
+export function exportCourseReport(query) {
+  return request({
+    url: '/company/statistics/exportFsCourseReportVO',
+    method: 'get',
+    params: query
+  })
+}

+ 9 - 0
src/api/his/storePayment.js

@@ -9,6 +9,15 @@ export function listStorePayment(query) {
   })
 }
 
+// 查询支付明细列表
+export function listStorePaymentError(query) {
+  return request({
+    url: '/his/storePayment/error/list',
+    method: 'get',
+    params: query
+  })
+}
+
 // 查询支付明细详细
 export function getStorePayment(paymentId) {
   return request({

+ 2 - 2
src/api/his/vessel.js

@@ -25,7 +25,7 @@ export function getVessel(id) {
 // 下载导入模板
 export function importTemplate() {
   return request({
-    url: '/his/questions/importTemplate',
+    url: '/his/vessel/importTemplate',
     method: 'get'
   })
 }
@@ -62,4 +62,4 @@ export function exportVessel(query) {
     method: 'get',
     params: query
   })
-}
+}

+ 34 - 1
src/api/hisStore/storeOrder.js

@@ -103,7 +103,13 @@ export function updateStoreOrder(data) {
     data: data
   })
 }
-
+// 修改订单ItemJson
+export function updateStoreOrderItemJson(id,backendEditProductType) {
+  return request({
+    url: '/store/store/storeOrder/updateStoreOrderItemJson/'+id + "/" + backendEditProductType,
+    method: 'get'
+  })
+}
 // 修改物流编号
 export function updateDeliveryId(data) {
   return request({
@@ -423,3 +429,30 @@ export function batchDeliveryAllPendingOrders(shipmentType = 4) {
     data: { shipmentType }
   })
 }
+
+// 订单确认审核
+export function auditStoreOrder(data) {
+  return request({
+    url: '/store/store/storeOrder/batchAudit',
+    method: 'post',
+    data: data
+  })
+}
+
+// 订单备注
+export function orderRemark(data) {
+  return request({
+    url: '/store/store/storeOrder/remark',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改订单代收金额
+export function editPayDelivery(data) {
+  return request({
+    url: '/store/store/storeOrder/editPayDelivery',
+    method: 'put',
+    data: data
+  })
+}

+ 9 - 0
src/api/hisStore/storeOrderItem.js

@@ -51,3 +51,12 @@ export function exportStoreOrderItem(query) {
     params: query
   })
 }
+
+// 修改订单数量
+export function updateNumStoreOrderItem(data) {
+  return request({
+    url: '/store/store/storeOrderItem/updateNum',
+    method: 'put',
+    data: data
+  })
+}

+ 2 - 2
src/api/live/liveData.js

@@ -28,9 +28,9 @@ export function getLiveDataDetailBySql(liveId) {
   })
 }
 
-export function getLiveUserDetailListBySql(liveId) {
+export function getLiveUserDetailListBySql(liveId, pageNum, pageSize) {
   return request({
-    url: '/liveData/liveData/getLiveUserDetailListBySql?liveId=' + liveId,
+    url: '/liveData/liveData/getLiveUserDetailListBySql?liveId=' + liveId + '&pageNum=' + (pageNum || 1) + '&pageSize=' + (pageSize || 100),
     method: 'get'
   })
 }

+ 9 - 0
src/api/live/liveMsg.js

@@ -59,3 +59,12 @@ export function exportLiveMsg(query) {
     params: query
   })
 }
+
+// 导出直播评论
+export function exportLiveMsgComments(liveId) {
+  return request({
+    url: '/live/liveMsg/exportComments/' + liveId,
+    method: 'get',
+    responseType: 'blob'
+  })
+}

+ 16 - 0
src/api/live/wxExpressTask.js

@@ -0,0 +1,16 @@
+import request from '@/utils/request'
+
+export function list(query) {
+  return request({
+    url: '/live/wxExpressTask/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function getFailedCount() {
+  return request({
+    url: '/live/wxExpressTask/getFailedCount',
+    method: 'get'
+  })
+}

+ 86 - 0
src/api/qw/QwWorkTaskNew.js

@@ -0,0 +1,86 @@
+import request from '@/utils/request'
+
+// 查询企微任务看板列表
+export function listQwWorkTask(query) {
+  return request({
+    url: '/qw/QwWorkTaskNew/list',
+    method: 'get',
+    params: query
+  })
+}
+export function deptListQwWorkTask(query) {
+  return request({
+    url: '/qw/QwWorkTaskNew/deptList',
+    method: 'get',
+    params: query
+  })
+}
+export function allListQwWorkTask(query) {
+  return request({
+    url: '/qw/QwWorkTaskNew/allList',
+    method: 'get',
+    params: query
+  })
+}
+export function glList(query) {
+  return request({
+    url: '/qw/QwWorkTaskNew/glList',
+    method: 'get',
+    params: query
+  })
+}
+// 查询企微任务看板详细
+export function getQwWorkTask(id) {
+  return request({
+    url: '/qw/QwWorkTaskNew/' + id,
+    method: 'get'
+  })
+}
+
+// 新增企微任务看板
+export function addQwWorkTask(data) {
+  return request({
+    url: '/qw/QwWorkTaskNew',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改企微任务看板
+export function updateQwWorkTask(data) {
+  return request({
+    url: '/qw/QwWorkTaskNew',
+    method: 'put',
+    data: data
+  })
+}
+export function updateQwWorkTask2(data) {
+  return request({
+    url: '/qw/QwWorkTaskNew/edit2',
+    method: 'put',
+    data: data
+  })
+}
+export function updateQwWorkTask3(data) {
+  return request({
+    url: '/qw/QwWorkTaskNew/edit3',
+    method: 'put',
+    data: data
+  })
+}
+// 删除企微任务看板
+export function delQwWorkTask(id) {
+  return request({
+    url: '/qw/QwWorkTaskNew/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出企微任务看板
+export function exportQwWorkTask(query) {
+  return request({
+    url: '/qw/QwWorkTaskNew/export',
+    method: 'get',
+    params: query
+  })
+}

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

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询自动打标签主列表
+export function listTags(query) {
+  return request({
+    url: '/qw/autoTags/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询自动打标签主详细
+export function getTags(id) {
+  return request({
+    url: '/qw/autoTags/' + id,
+    method: 'get'
+  })
+}
+
+// 新增自动打标签主
+export function addTags(data) {
+  return request({
+    url: '/qw/autoTags',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改自动打标签主
+export function updateTags(data) {
+  return request({
+    url: '/qw/autoTags',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除自动打标签主
+export function delTags(id) {
+  return request({
+    url: '/qw/autoTags/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出自动打标签主
+export function exportTags(query) {
+  return request({
+    url: '/qw/autoTags/export',
+    method: 'get',
+    params: query
+  })
+}

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

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询自动打标签的日志列表
+export function listAutoTagsLogs(query) {
+  return request({
+    url: '/qw/autoTagsLogs/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询自动打标签的日志详细
+export function getAutoTagsLogs(id) {
+  return request({
+    url: '/qw/autoTagsLogs/' + id,
+    method: 'get'
+  })
+}
+
+// 新增自动打标签的日志
+export function addAutoTagsLogs(data) {
+  return request({
+    url: '/qw/autoTagsLogs',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改自动打标签的日志
+export function updateAutoTagsLogs(data) {
+  return request({
+    url: '/qw/autoTagsLogs',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除自动打标签的日志
+export function delAutoTagsLogs(id) {
+  return request({
+    url: '/qw/autoTagsLogs/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出自动打标签的日志
+export function exportAutoTagsLogs(query) {
+  return request({
+    url: '/qw/autoTagsLogs/export',
+    method: 'get',
+    params: query
+  })
+}

+ 31 - 0
src/api/qw/friendWelcome.js

@@ -16,3 +16,34 @@ export function getFriendWelcome(id) {
     method: 'get'
   })
 }
+// 删除好友欢迎语
+export function delFriendWelcome(id) {
+  return request({
+    url: '/qw/friendWelcome/' + id,
+    method: 'delete'
+  })
+}
+// 新增好友欢迎语
+export function addFriendWelcome(data) {
+  return request({
+    url: '/qw/friendWelcome',
+    method: 'post',
+    data: data
+  })
+}
+// 修改好友欢迎语
+export function updateFriendWelcome(data) {
+  return request({
+    url: '/qw/friendWelcome',
+    method: 'put',
+    data: data
+  })
+}
+// 导出好友欢迎语
+export function exportFriendWelcome(query) {
+  return request({
+    url: '/qw/friendWelcome/export',
+    method: 'get',
+    params: query
+  })
+}

+ 62 - 0
src/api/qw/groupChat.js

@@ -0,0 +1,62 @@
+import request from '@/utils/request'
+
+// 查询客户群详情列表
+export function listGroupChat(query) {
+  return request({
+    url: '/qw/groupChat/list',
+    method: 'get',
+    params: query
+  })
+}
+export function listGroupChatDeptList(query) {
+  return request({
+    url: '/qw/groupChat/deptList',
+    method: 'get',
+    params: query
+  })
+}
+export function listGroupChatMyList(query) {
+  return request({
+    url: '/qw/groupChat/myList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询客户群详情详细
+export function getGroupChat(chatId) {
+  return request({
+    url: '/qw/groupChat/' + chatId,
+    method: 'get'
+  })
+}
+export function allList(corpId) {
+  return request({
+    url: '/qw/groupChat/allList/' + corpId,
+    method: 'get'
+  })
+}
+// 同步客户群详情
+export function cogradientGroupChat(corpId) {
+  return request({
+    url: '/qw/groupChat/cogradientGroupChat/'+corpId,
+    method: 'get',
+  })
+}
+
+export function cogradientMyGroupChat(corpId) {
+  return request({
+    url: '/qw/groupChat/cogradientMyGroupChat/'+corpId,
+    method: 'get',
+  })
+}
+
+
+
+export function listAll(qwUserIds, corpId, sopId) {
+  return request({
+    url: '/qw/groupChat/listAll',
+    method: 'get',
+    params:{qwUserIds, corpId, sopId}
+  })
+}

+ 93 - 0
src/api/qw/groupMsg.js

@@ -0,0 +1,93 @@
+import request from '@/utils/request'
+
+// 查询客户群发记录主列表
+export function listGroupMsg(query) {
+  return request({
+    url: '/qw/groupMsg/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询客户群发记录主详细
+export function getGroupMsg(id) {
+  return request({
+    url: '/qw/groupMsg/' + id,
+    method: 'get'
+  })
+}
+
+
+// 新增客户群发记录主
+export function addGroupMsg(data) {
+  return request({
+    url: '/qw/groupMsg',
+    method: 'post',
+    data: data
+  })
+}
+
+/** 统计数据详情,已发送,未发送,已接收,未接收等 */
+export function countGroupMsgUser(groupMsgId){
+  return request({
+    url: '/qw/groupMsg/getCountGroupMsgUser/' + groupMsgId,
+    method: 'get'
+  })
+}
+
+
+/** 统计数据详情,已发送群主,送达群聊,未发送群主,未送达群聊等 */
+export function CountGroupMsgBaseUser(groupMsgId){
+  return request({
+    url: '/qw/groupMsg/getCountGroupMsgBaseUser/' + groupMsgId,
+    method: 'get'
+  })
+}
+
+
+/** 客户群发 发送/接收的数据详情 */
+export function CountGroupMsgUserDetails(query){
+  return request({
+    url: '/qw/groupMsg/getCountGroupMsgUserDetails',
+    method: 'get',
+    params: query
+  })
+}
+
+
+
+/** 提醒成员群发 */
+
+export function remindGroupMsg(query){
+  return request({
+    url: '/qw/groupMsg/remindGroupMsg',
+    method: 'get',
+    params: query
+  })
+}
+
+// 修改客户群发记录主
+export function updateGroupMsg(data) {
+  return request({
+    url: '/qw/groupMsg',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除客户群发记录主
+export function delGroupMsg(id) {
+  return request({
+    url: '/qw/groupMsg/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出客户群发记录主
+export function exportGroupMsg(query) {
+  return request({
+    url: '/qw/groupMsg/export',
+    method: 'get',
+    params: query
+  })
+}

+ 71 - 0
src/api/qw/groupMsgUser.js

@@ -0,0 +1,71 @@
+import request from '@/utils/request'
+
+// 查询群发成员发送任务及执行结果反馈记录列表
+export function listGroupMsgUser(query) {
+  return request({
+    url: '/qw/groupMsgUser/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询群发成员发送任务及执行结果反馈记录详细
+export function getGroupMsgUser(id) {
+  return request({
+    url: '/qw/groupMsgUser/' + id,
+    method: 'get'
+  })
+}
+
+// 新增群发成员发送任务及执行结果反馈记录
+export function addGroupMsgUser(data) {
+  return request({
+    url: '/qw/groupMsgUser',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改群发成员发送任务及执行结果反馈记录
+export function updateGroupMsgUser(data) {
+  return request({
+    url: '/qw/groupMsgUser',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除群发成员发送任务及执行结果反馈记录
+export function delGroupMsgUser(id) {
+  return request({
+    url: '/qw/groupMsgUser/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出群发成员发送任务及执行结果反馈记录
+export function exportGroupMsgUser(query) {
+  return request({
+    url: '/qw/groupMsgUser/export',
+    method: 'get',
+    params: query
+  })
+}
+
+/** 刷新/同步 客户群群发 结果 */
+export function refreshResultsGroupMsgUser(data){
+  return request({
+    url: '/qw/groupMsgUser/refreshResultsGroupMsgUser',
+    method: 'post',
+    data: data
+  })
+}
+
+/** 刷新/同步 客户群发 结果 */
+export function refreshResultsMsgUser(data){
+  return request({
+    url: '/qw/groupMsgUser/refreshResultsMsgUser',
+    method: 'post',
+    data: data
+  })
+}

+ 74 - 0
src/api/qw/group_chat_user.js

@@ -0,0 +1,74 @@
+import request from '@/utils/request'
+
+// 查询客户群成员列列表
+export function listGroup_chat_user(query) {
+  return request({
+    url: '/qw/group_chat_user/list',
+    method: 'get',
+    params: query
+  })
+}
+
+//获取日星月的入群退群统计
+export function getDataWeekMonthCount(query) {
+  return request({
+    url: '/qw/group_chat_user/getDataWeekMonthCount',
+    method: 'get',
+    params: query
+  })
+}
+
+
+// 查询客户群成员列详细
+export function getGroup_chat_user(id) {
+  return request({
+    url: '/qw/group_chat_user/' + id,
+    method: 'get'
+  })
+}
+
+// 新增客户群成员列
+export function addGroup_chat_user(data) {
+  return request({
+    url: '/qw/group_chat_user',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改客户群成员列
+export function updateGroup_chat_user(data) {
+  return request({
+    url: '/qw/group_chat_user',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除客户群成员列
+export function delGroup_chat_user(id) {
+  return request({
+    url: '/qw/group_chat_user/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出客户群成员列
+export function exportGroup_chat_user(query) {
+  return request({
+    url: '/qw/group_chat_user/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 同步客户群详情
+export function cogradientGroupChatUser(data) {
+  return request({
+    url: '/qw/group_chat_user/cogradientGroupChatUser',
+    method: 'put',
+    data:data
+  })
+}
+
+

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

@@ -16,3 +16,12 @@ export function  getQwUserInfo(query) {
     params: query
   })
 }
+
+// 根据条件查询企微用户列表
+export function getQwList(params) {
+  return request({
+    url: '/qw/user/qwCompanyList',
+    method: 'post',
+    data: params
+  })
+}

+ 34 - 0
src/api/qw/qwUserVoiceLogTotal.js

@@ -0,0 +1,34 @@
+import request from '@/utils/request'
+
+// 查询企微用户通话记录列表
+export function listQwUserVoiceLogTotal(query) {
+  return request({
+    url: '/qw/qwUserVoiceLog/totalList',
+    method: 'get',
+    params: query
+  })
+}
+
+export function listQwUserVoiceLogSellTotal(query) {
+  return request({
+    url: '/qw/qwUserVoiceLog/sellTotalList',
+    method: 'get',
+    params: query
+  })
+}
+export function exportQwUserVoiceLogSellTotal(query) {
+  return request({
+    url: '/qw/qwUserVoiceLog/sellTotalExport',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出企微用户通话记录
+export function exportQwUserVoiceLogTotal(query) {
+  return request({
+    url: '/qw/qwUserVoiceLog/totalExport',
+    method: 'get',
+    params: query
+  })
+}

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

@@ -73,3 +73,18 @@ export function exportTagGroup(query) {
     params: query
   })
 }
+// 查询企微客户标签列表
+export function listTag(query) {
+  return request({
+    url: '/qw/tag/list',
+    method: 'get',
+    params: query
+  })
+}
+export function searchTags(data) {
+  return request({
+    url: '/qw/tag/searchTags',
+    method: 'post',
+    data: data
+  })
+}

+ 156 - 3
src/api/qw/user.js

@@ -8,6 +8,14 @@ export function staffListUser(query) {
     params: query
   })
 }
+// 查询企微员工列表
+export function staffListPost(query) {
+  return request({
+    url: '/qw/user/staffListPost',
+    method: 'get',
+    data: query
+  })
+}
 
 // 查询企微用户列表
 export function listUser(query) {
@@ -47,11 +55,34 @@ export function getMyQwUserList(query) {
 }
 export function getMyQwCompanyList(query) {
   return request({
-    url: '/qw/user/getMyQwCompanyList',
-    method: 'get',
-    params: query
+    url: '/qw/user/getMyQwCompanyList/' + query,
+    method: 'get'
   })
 }
+
+export function getMyQwCompanyListAll() {
+  return request({
+    url: '/qw/user/getMyQwCompanyListAll',
+    method: 'get'
+  })
+}
+export function getCompanyListByCorpId(id) {
+  return request({
+    url: '/company/companyUser/getCompanyList/'+id,
+    method: 'get'
+  })
+}
+
+
+
+export function getQwCompanyList() {
+  return request({
+    url: '/qw/user/getQwCompanyList',
+    method: 'get'
+  })
+}
+
+
 // 查询企微用户详细
 export function getQwUser(id) {
   return request({
@@ -272,3 +303,125 @@ export function getQwAllUserList(query) {
     params: query
   })
 }
+
+export function updateIsAuto(data) {
+  return request({
+    url: '/qw/user/updateIsAuto',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 登录企业微信(发起登录)
+ */
+export function loginQwIpad(data) {
+  return request({
+    url: '/qw/user/loginQwIpad',
+    method: 'post',
+    data: data
+  })
+}
+export function twoCode(data) {
+  return request({
+    url: '/qw/user/twoCode',
+    method: 'post',
+    data: data
+  })
+}
+export function twoCodeStatus(data) {
+  return request({
+    url: '/qw/user/twoCodeStatus',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 获取登录状态
+ */
+export function qrCodeStatus(data) {
+  return request({
+    url: '/qw/user/qrCodeStatus',
+    method: 'post',
+    data: data
+  })
+}
+
+export function updateSendType(data) {
+  return request({
+    url: '/qw/user/updateSendType',
+    method: 'post',
+    data: data
+  })
+}
+/**
+ * 登录企业微信(发起登录)
+ */
+export function getQwIpad(data) {
+  return request({
+    url: '/qw/user/getQwIpad',
+    method: 'post',
+    data: data
+  })
+}
+export function delQwIpad(data) {
+  return request({
+    url: '/qw/user/delQwIpad',
+    method: 'post',
+    data: data
+  })
+}
+
+export function qrCodeVerify(data) {
+  return request({
+    url: '/qw/user/qrCodeVerify',
+    method: 'post',
+    data: data
+  })
+}
+
+export function outLoginQwIpad(data) {
+  return request({
+    url: '/qw/user/outLoginQwIpad',
+    method: 'post',
+    data: data
+  })
+}
+
+export function changeVideoStatus(id) {
+  return request({
+    url: '/qw/user/changeVideoStatus',
+    method: 'get',
+    params: {id}
+  })
+}
+
+
+// 分配异地主机
+export function handleAllocateRemoteHost(data) {
+  return request({
+    url: '/qw/user/allocateRemoteHost',
+    method: 'post',
+    data
+  });
+}
+
+// 导出企微员工
+export function exportStaff(query) {
+  return request({
+    url: '/qw/user/exportStaff',
+    method: 'get',
+    params: query
+  })
+}
+
+/**
+ * 修改ai客服上下线
+ */
+export function updateFastGptRoleStatusById(id) {
+  return request({
+    url: '/qw/user/updateFastGptRoleStatusById/' + id,
+    method: 'get'
+  })
+}

+ 29 - 0
src/api/user/integral.js

@@ -0,0 +1,29 @@
+import request from '@/utils/request'
+
+// 查询用户列表
+export function listUser(query) {
+  return request({
+    url: '/user/integral/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询用户积分明细
+export function getIntegralLogs(userId, params = {}) {
+  return request({
+    url: '/user/integral/logs/' + userId,
+    method: 'get',
+    params: params
+  })
+}
+
+// 添加积分
+export function addIntegral(data) {
+  return request({
+    url: '/user/integral/add',
+    method: 'post',
+    data: data
+  })
+}
+

BIN
src/assets/logo/xcsw.png


BIN
src/assets/logo/zlwh.png


+ 1 - 2
src/components/TreeSelect/index.vue

@@ -73,7 +73,7 @@
                   :value="checkedKeysSet.has(item.key)"
                   :disabled="checkboxDisabled(item)"
                   @change="handleCheckboxChange(item, $event)"
-                  @click.native.stop
+                  @click.stop
                   class="node-checkbox"
                 ></el-checkbox>
 
@@ -569,7 +569,6 @@ export default {
       }
     },
     handleCheckboxChange(node, checked) {
-
       if (this.checkboxDisabled(node)) return;
 
       const newCheckedKeys = new Set(this.checkedKeysSet);

+ 1050 - 0
src/components/TreeSelect/indexOld.vue

@@ -0,0 +1,1050 @@
+<template>
+  <div class="task-select-tree" :style="{ width: componentWidth }">
+    <el-popover
+      ref="popover"
+      placement="bottom-start"
+      :width="popoverComputedWidth"
+      trigger="click"
+      v-model="popoverVisible"
+      popper-class="task-select-tree-popper"
+      :disabled="disabled"
+      @show="onPopoverShow"
+    >
+      <div class="popover-content-wrapper">
+        <el-input
+          v-if="filterable"
+          placeholder="输入关键字过滤"
+          v-model="filterText"
+          size="mini"
+          clearable
+          class="filter-input"
+          @input="handleFilterDebounced"
+        >
+        </el-input>
+
+        <div class="tree-container" :style="{ height: treeHeight + 'px' }">
+          <div
+            ref="virtualList"
+            class="virtual-tree-list"
+            @scroll="handleScroll"
+            :style="{ height: treeHeight + 'px', overflow: 'auto' }"
+          >
+            <div
+              class="virtual-content"
+              :style="{
+                height: totalHeight + 'px',
+                position: 'relative',
+                paddingTop: offsetY + 'px'
+              }"
+            >
+              <div
+                v-for="(item, index) in visibleItems"
+                :key="`${item.key}-${item.level}-${index}`"
+                :style="{
+                  height: itemHeight + 'px',
+                  paddingLeft: (item.level * indentSize) + 'px',
+                  display: 'flex',
+                  alignItems: 'center',
+                  position: 'relative'
+                }"
+                :class="[
+                  'virtual-tree-node',
+                  {
+                    'is-leaf': item.isLeaf,
+                    'is-checked': checkedKeysSet.has(item.key) && (item.isLeaf || parentSelectable || !checkStrictly),
+                    'is-disabled': item.nodeDisabled || checkboxDisabled(item) // Combined disabled state for row styling
+                  }
+                ]"
+                @click="handleNodeClick(item)"
+              >
+                <div class="node-expand-container">
+                  <i
+                    v-if="!item.isLeaf"
+                    :class="[
+                      'node-expand-icon',
+                      item.expanded ? 'el-icon-caret-bottom' : 'el-icon-caret-right'
+                    ]"
+                    @click.stop="toggleNodeExpand(item)"
+                  ></i>
+                  <span v-else class="node-expand-placeholder"></span>
+                </div>
+
+                <el-checkbox
+                  :value="checkedKeysSet.has(item.key)"
+                  :disabled="checkboxDisabled(item)"
+                  @change="handleCheckboxChange(item, $event)"
+                  @click.native.stop
+                  class="node-checkbox"
+                ></el-checkbox>
+
+                <span class="node-label" :title="item.label">{{ item.label }}</span>
+
+                <span v-if="item.isLeaf && showNodeDetail" class="node-detail-text">
+                  (ID: {{ item.originalData.qwUserId || item.originalData.id }}{{ item.originalData.taskExecDate ? ' ' + item.originalData.taskExecDate : '' }})
+                </span>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div class="popover-footer">
+          <span class="selected-count">已选择: {{ displaySelectedCount }}</span>
+          <el-button size="mini" @click="handleClearInPopover">清空</el-button>
+          <el-button
+            size="mini"
+            :type="isAllCurrentResultsSelected ? 'warning' : 'info'"
+            @click="handleSelectAllToggle"
+          >
+            {{ isAllCurrentResultsSelected ? '取消全选' : '全选' }}
+          </el-button>
+          <el-button type="primary" size="mini" @click="handleConfirm">确定</el-button>
+        </div>
+      </div>
+
+      <div
+        slot="reference"
+        :class="['select-trigger-wrapper', { 'is-disabled': disabled, 'is-active': popoverVisible }]"
+        @mouseenter="inputHovering = true"
+        @mouseleave="inputHovering = false"
+      >
+        <div class="tags-or-value-container">
+          <template v-if="multiple && displaySelectedNodes.length > 0">
+            <el-tag
+              v-for="node in displaySelectedNodes"
+              :key="node.key"
+              type="info"
+              size="small"
+              closable
+              :disable-transitions="true"
+              @close.stop="removeTag(node)"
+              class="trigger-tag"
+            >
+              {{ node.label }}
+            </el-tag>
+            <el-tag
+              v-if="displaySelectedCount > maxDisplayTags && displaySelectedNodes.length === maxDisplayTags"
+              type="info"
+              size="small"
+              class="trigger-tag"
+            >
+              + {{ displaySelectedCount - maxDisplayTags }}
+            </el-tag>
+          </template>
+          <span v-else-if="!multiple && displaySelectedCount > 0" class="single-value-display">
+            {{ singleSelectedLabel }}
+          </span>
+          <span v-else class="placeholder-text">{{ placeholder }}</span>
+        </div>
+
+        <span class="icons-container">
+          <i
+            v-if="showClearIcon"
+            class="el-icon-circle-close clear-icon"
+            @click.stop="handleClearOnTrigger"
+          ></i>
+          <i :class="['el-icon-arrow-up', 'arrow-icon', { 'is-reverse': !popoverVisible }]"></i>
+        </span>
+      </div>
+    </el-popover>
+  </div>
+</template>
+
+<script>
+// 防抖函数
+function debounce(func, wait) {
+  let timeout;
+  return function executedFunction(...args) {
+    const later = () => {
+      clearTimeout(timeout);
+      func(...args);
+    };
+    clearTimeout(timeout);
+    timeout = setTimeout(later, wait);
+  };
+}
+
+export default {
+  name: 'OptimizedSelectTree',
+  props: {
+    value: {
+      type: Array,
+      default: () => []
+    },
+    rawData: {
+      type: Array,
+      required: true
+    },
+    placeholder: {
+      type: String,
+      default: '请选择'
+    },
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    disabled: { // Global disabled for the component
+      type: Boolean,
+      default: false
+    },
+    checkStrictly: {
+      type: Boolean,
+      default: false // Important for parent-child linkage
+    },
+    filterable: {
+      type: Boolean,
+      default: true
+    },
+    maxDisplayTags: {
+      type: Number,
+      default: 2
+    },
+    componentWidth: {
+      type: String,
+      default: '100%'
+    },
+    popoverWidth: {
+      type: [String, Number],
+      default: 'auto'
+    },
+    treeProps: {
+      type: Object,
+      default: () => ({
+        children: 'children',
+        label: 'label',
+        isLeaf: 'isLeaf',
+        disabled: 'disabled' // Key for disabled state in rawData
+      })
+    },
+    nodeKey: {
+      type: String,
+      default: 'id'
+    },
+    defaultExpandAll: {
+      type: Boolean,
+      default: false
+    },
+    showNodeDetail: {
+      type: Boolean,
+      default: true
+    },
+    treeHeight: {
+      type: Number,
+      default: 280
+    },
+    returnLeafOnly: {
+      // This prop's effect on output is now overridden.
+      // It might still be used by consumers for other logic if they rely on its original meaning.
+      type: Boolean,
+      default: true
+    },
+    parentSelectable: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      popoverVisible: false,
+      filterText: '',
+      inputHovering: false,
+      triggerElRect: null,
+      itemHeight: 36,
+      indentSize: 24,
+      startIndex: 0,
+      endIndex: 0,
+      scrollTop: 0,
+      visibleItemCount: 0,
+      flattenedNodes: [],
+      nodeMap: new Map(),
+      checkedKeysSet: new Set(),
+      expandedKeysSet: new Set(),
+      updateTimer: null
+    };
+  },
+  computed: {
+    popoverComputedWidth() {
+      if (this.popoverWidth === 'auto') {
+        return this.triggerElRect ? this.triggerElRect.width : 300;
+      }
+      return parseInt(this.popoverWidth, 10);
+    },
+    totalHeight() {
+      return this.visibleNodes.length * this.itemHeight;
+    },
+    offsetY() {
+      return this.startIndex * this.itemHeight;
+    },
+    visibleNodes() {
+      let nodes = this.flattenedNodes;
+      if (this.filterText.trim()) {
+        nodes = this.applyFilter(this.flattenedNodes);
+      }
+
+      const visible = [];
+      for (const node of nodes) {
+        if (this.isNodeCurrentlyVisible(node)) {
+          visible.push(node);
+        }
+      }
+      return visible;
+    },
+    visibleItems() {
+      const items = this.visibleNodes.slice(this.startIndex, this.endIndex);
+      return items.map(node => ({
+        ...node,
+        expanded: this.expandedKeysSet.has(node.key)
+      }));
+    },
+    // Actual count of selected leaf items
+    displaySelectedCount() {
+      let leafCount = 0;
+      this.checkedKeysSet.forEach(key => {
+        const node = this.nodeMap.get(key);
+        if (node && node.isLeaf) {
+          leafCount++;
+        }
+      });
+      return leafCount;
+    },
+    displaySelectedNodes() {
+      if (!this.multiple) return [];
+      const resultNodes = [];
+      Array.from(this.checkedKeysSet)
+        .map(key => this.nodeMap.get(key))
+        .filter(Boolean)
+        .forEach(node => {
+          // Always display tags for leaf nodes that are selected.
+          if (node.isLeaf) {
+            resultNodes.push(node);
+          }
+        });
+      return resultNodes.slice(0, this.maxDisplayTags);
+    },
+    singleSelectedLabel() {
+      if (this.multiple || this.value.length === 0) return '';
+      // 'value' prop should only contain leaf keys due to emitChange modification.
+      // For single select, value should have at most one key.
+      const selectedKey = String(this.value[0]);
+      const node = this.nodeMap.get(selectedKey);
+      // We expect 'value' to contain a leaf key.
+      return node ? node.label : '';
+    },
+    showClearIcon() {
+      return (
+        !this.disabled &&
+        this.inputHovering &&
+        this.displaySelectedCount > 0 && // Based on leaf count now
+        !this.multiple
+      );
+    },
+    // 计算当前搜索结果中的所有叶子节点
+    currentResultLeafNodes() {
+      return this.visibleNodes.filter(node => node.isLeaf && !this.checkboxDisabled(node));
+    },
+    // 判断当前搜索结果是否全部选中
+    isAllCurrentResultsSelected() {
+      const leafNodes = this.currentResultLeafNodes;
+      if (leafNodes.length === 0) return false;
+      return leafNodes.every(node => this.checkedKeysSet.has(node.key));
+    }
+  },
+  watch: {
+    rawData: {
+      handler(newData) {
+        this.processRawData(newData);
+        this.syncCheckedKeysFromValue();
+      },
+      immediate: true
+    },
+    value: {
+      handler() {
+        this.syncCheckedKeysFromValue();
+      },
+      deep: true,
+    },
+    filterText: {
+      handler() {
+        this.resetScroll();
+      }
+    },
+    popoverVisible(isVisible) {
+      if (isVisible) {
+        this.$nextTick(() => {
+          this.calculateVisibleRange();
+        });
+      }
+    }
+  },
+  created() {
+    this.handleFilterDebounced = debounce(this.filterTextChange, 300);
+    this.visibleItemCount = Math.ceil(this.treeHeight / this.itemHeight) + 5;
+  },
+  methods: {
+    filterTextChange() {
+      this.resetScroll();
+    },
+    processRawData(data) {
+      if (!Array.isArray(data)) {
+        this.flattenedNodes = [];
+        this.nodeMap.clear();
+        return;
+      }
+
+      const flattened = [];
+      const nodeMap = new Map();
+      const expandedKeys = new Set();
+
+      const processNode = (node, level = 0, parentKey = null) => {
+        const children = node[this.treeProps.children] || [];
+        const hasChildren = children.length > 0;
+        const key = node[this.nodeKey];
+
+        if (key === undefined || key === null) {
+          console.warn('Node lacks a unique key:', node);
+          return;
+        }
+        const strKey = String(key);
+
+        let label = node[this.treeProps.label];
+        if (typeof label !== 'string' || label.trim() === '') {
+          label = node.name || node.title || node.text || `Node-${strKey}`;
+        }
+
+        const nodeDisabledByData = !!node[this.treeProps.disabled];
+
+        const processedNode = {
+          key: strKey,
+          label: String(label).trim(),
+          level,
+          isLeaf: !hasChildren,
+          nodeDisabled: nodeDisabledByData,
+          parentKey: parentKey ? String(parentKey) : null,
+          childrenKeys: hasChildren ? children.map(child => String(child[this.nodeKey])).filter(k => k !== undefined && k !== null) : [],
+          originalData: node,
+          hasChildren
+        };
+
+        flattened.push(processedNode);
+        nodeMap.set(processedNode.key, processedNode);
+
+        if (this.defaultExpandAll && hasChildren && !nodeDisabledByData) {
+          expandedKeys.add(processedNode.key);
+        }
+
+        if (hasChildren) {
+          children.forEach(child => processNode(child, level + 1, strKey));
+        }
+      };
+
+      data.forEach(node => processNode(node));
+
+      this.flattenedNodes = flattened;
+      this.nodeMap = nodeMap;
+      this.expandedKeysSet = new Set(expandedKeys);
+    },
+
+    isNodeCurrentlyVisible(node, currentExpandedSet = this.expandedKeysSet) {
+      if (node.level === 0) return true;
+      let current = node;
+      while (current.parentKey) {
+        const parent = this.nodeMap.get(current.parentKey);
+        if (!parent || !currentExpandedSet.has(parent.key)) {
+          return false;
+        }
+        current = parent;
+      }
+      return true;
+    },
+
+    applyFilter(nodesToFilter) {
+      const searchText = this.filterText.toLowerCase().trim();
+      if (!searchText) return nodesToFilter;
+
+      const matchedNodeKeys = new Set();
+      const newExpandedKeys = new Set(this.expandedKeysSet);
+
+      nodesToFilter.forEach(node => {
+        if (node.label.toLowerCase().includes(searchText)) {
+          matchedNodeKeys.add(node.key);
+          let parentKey = node.parentKey;
+          while (parentKey) {
+            const parentNode = this.nodeMap.get(parentKey);
+            if (parentNode) {
+              newExpandedKeys.add(parentKey);
+              parentKey = parentNode.parentKey;
+            } else {
+              break;
+            }
+          }
+        }
+      });
+
+      const filteredResult = nodesToFilter.filter(node => {
+        return this.hasMatchInSubtreeOrSelf(node, searchText);
+      });
+
+      this.expandedKeysSet = newExpandedKeys;
+      return filteredResult;
+    },
+    hasMatchInSubtreeOrSelf(node, searchText) {
+      if (node.label.toLowerCase().includes(searchText)) {
+        return true;
+      }
+      if (!node.isLeaf) {
+        for (const childKey of node.childrenKeys) {
+          const childNode = this.nodeMap.get(childKey);
+          if (childNode && this.hasMatchInSubtreeOrSelf(childNode, searchText)) {
+            return true;
+          }
+        }
+      }
+      return false;
+    },
+    checkboxDisabled(item) {
+      if (item.nodeDisabled) return true;
+      if (this.checkStrictly && !item.isLeaf && !this.parentSelectable) {
+        return true;
+      }
+      return false;
+    },
+
+    calculateVisibleRange() {
+      const containerHeight = this.treeHeight;
+      const totalItems = this.visibleNodes.length;
+
+      const newStartIndex = Math.floor(this.scrollTop / this.itemHeight);
+      const visibleCountInViewport = Math.ceil(containerHeight / this.itemHeight);
+
+      const buffer = 5;
+      this.startIndex = Math.max(0, newStartIndex - buffer);
+      this.endIndex = Math.min(totalItems, newStartIndex + visibleCountInViewport + buffer);
+    },
+    handleScroll(e) {
+      const newScrollTop = e.target.scrollTop;
+      if (newScrollTop !== this.scrollTop) {
+        this.scrollTop = newScrollTop;
+        if (!this.scrollRAF) {
+          this.scrollRAF = requestAnimationFrame(() => {
+            this.calculateVisibleRange();
+            this.scrollRAF = null;
+          });
+        }
+      }
+    },
+    resetScroll() {
+      this.scrollTop = 0;
+      if (this.$refs.virtualList) {
+        this.$refs.virtualList.scrollTop = 0;
+      }
+      this.$nextTick(() => {
+        this.calculateVisibleRange();
+      });
+    },
+    toggleNodeExpand(node) {
+      if (node.isLeaf || node.nodeDisabled) return;
+      const key = node.key;
+      if (this.expandedKeysSet.has(key)) {
+        this.expandedKeysSet.delete(key);
+      } else {
+        this.expandedKeysSet.add(key);
+      }
+      this.expandedKeysSet = new Set(this.expandedKeysSet);
+      this.$nextTick(() => this.calculateVisibleRange());
+    },
+    handleNodeClick(node) {
+      if (node.nodeDisabled) return;
+
+      if (!node.isLeaf && !this.parentSelectable) {
+        this.toggleNodeExpand(node);
+        return;
+      }
+      if (!node.isLeaf) {
+        this.toggleNodeExpand(node);
+        return;
+      }
+
+      if (!this.checkboxDisabled(node)) {
+        const isCurrentlyChecked = this.checkedKeysSet.has(node.key);
+        this.handleCheckboxChange(node, !isCurrentlyChecked);
+      }
+    },
+    handleCheckboxChange(node, checked) {
+
+      if (this.checkboxDisabled(node)) return;
+
+      const newCheckedKeys = new Set(this.checkedKeysSet);
+      const canNodeItselfBeInSet = node.isLeaf || this.parentSelectable || !this.checkStrictly;
+
+      if (checked) {
+        if (!this.multiple) {
+          newCheckedKeys.clear();
+        }
+        if (canNodeItselfBeInSet) {
+          newCheckedKeys.add(node.key);
+        }
+        if (!this.checkStrictly) {
+          this.selectAllChildren(node, newCheckedKeys);
+          this.checkParentSelection(node, newCheckedKeys);
+        }
+      } else {
+        if (canNodeItselfBeInSet) {
+          newCheckedKeys.delete(node.key);
+        }
+        if (!this.checkStrictly) {
+          this.unselectAllChildren(node, newCheckedKeys);
+          this.uncheckParentSelection(node, newCheckedKeys);
+        }
+      }
+      this.checkedKeysSet = newCheckedKeys;
+      this.emitChange();
+    },
+    selectAllChildren(node, checkedKeys) {
+      if (node.childrenKeys && node.childrenKeys.length > 0) {
+        node.childrenKeys.forEach(childKey => {
+          const childNode = this.nodeMap.get(childKey);
+          if (childNode && !childNode.nodeDisabled) {
+            const canChildBeInSet = childNode.isLeaf || this.parentSelectable || !this.checkStrictly;
+            if(canChildBeInSet){
+              checkedKeys.add(childKey);
+            }
+            if (!childNode.isLeaf) {
+              this.selectAllChildren(childNode, checkedKeys);
+            }
+          }
+        });
+      }
+    },
+    unselectAllChildren(node, checkedKeys) {
+      if (node.childrenKeys && node.childrenKeys.length > 0) {
+        node.childrenKeys.forEach(childKey => {
+          const childNode = this.nodeMap.get(childKey);
+          if (childNode) {
+            const canChildBeInSet = childNode.isLeaf || this.parentSelectable || !this.checkStrictly;
+            if(canChildBeInSet){
+              checkedKeys.delete(childKey);
+            }
+            if (!childNode.isLeaf) {
+              this.unselectAllChildren(childNode, checkedKeys);
+            }
+          }
+        });
+      }
+    },
+    checkParentSelection(node, checkedKeys) {
+      if (!node.parentKey || this.checkStrictly) return;
+
+      const parentNode = this.nodeMap.get(node.parentKey);
+      if (parentNode && !parentNode.nodeDisabled && (this.parentSelectable || !this.checkStrictly)) {
+        const allChildrenChecked = parentNode.childrenKeys.every(childKey => {
+          const child = this.nodeMap.get(childKey);
+          return !child || child.nodeDisabled || checkedKeys.has(childKey);
+        });
+
+        if (allChildrenChecked) {
+          checkedKeys.add(parentNode.key);
+          this.checkParentSelection(parentNode, checkedKeys);
+        }
+      }
+    },
+    uncheckParentSelection(node, checkedKeys) {
+      if (!node.parentKey || this.checkStrictly) return;
+      const parentNode = this.nodeMap.get(node.parentKey);
+      if (parentNode && (this.parentSelectable || !this.checkStrictly)) {
+        if (checkedKeys.has(parentNode.key)) {
+          checkedKeys.delete(parentNode.key);
+          this.uncheckParentSelection(parentNode, checkedKeys);
+        }
+      }
+    },
+    emitChange() {
+      let finalKeys = [];
+      const currentInternalCheckedKeys = Array.from(this.checkedKeysSet);
+
+      // ALWAYS return only leaf nodes in the emitted value.
+      currentInternalCheckedKeys.forEach(key => {
+        const node = this.nodeMap.get(key);
+        if (node && node.isLeaf) {
+          finalKeys.push(key);
+        }
+      });
+
+      finalKeys = [...new Set(finalKeys)]; // Deduplicate
+      const finalNodes = finalKeys.map(k => this.nodeMap.get(k)).filter(Boolean);
+
+      this.$emit('input', finalKeys);
+      this.$emit('change', finalKeys, finalNodes);
+    },
+    syncCheckedKeysFromValue() {
+      const newCheckedKeys = new Set();
+      if (Array.isArray(this.value)) {
+        this.value.forEach(key => {
+          const strKey = String(key);
+          const node = this.nodeMap.get(strKey);
+          if (node) {
+            // Value should ideally contain leaf keys if controlled by this component.
+            // If an external value has a parent key, handle it for internal consistency.
+            newCheckedKeys.add(strKey);
+            if (!node.isLeaf && !this.checkStrictly) {
+              this.selectAllChildren(node, newCheckedKeys);
+              this.checkParentSelection(node, newCheckedKeys);
+            } else if (node.isLeaf && !this.checkStrictly) {
+              // If a leaf is added from value, ensure its parent's state is updated
+              this.checkParentSelection(node, newCheckedKeys);
+            }
+          }
+        });
+      }
+      this.checkedKeysSet = newCheckedKeys;
+
+      if(this.popoverVisible) {
+        this.$nextTick(() => this.calculateVisibleRange());
+      }
+    },
+    handleConfirm() {
+      this.emitChange(); // emitChange now handles filtering for leaf nodes
+      this.popoverVisible = false;
+    },
+    handleClearInPopover() {
+      this.checkedKeysSet.clear();
+      this.emitChange();
+    },
+    handleClearOnTrigger() {
+      if (this.disabled) return;
+      this.checkedKeysSet.clear();
+      this.emitChange();
+      this.popoverVisible = false;
+    },
+    // 新增:处理全选/取消全选功能
+    handleSelectAllToggle() {
+      if (this.disabled) return;
+
+      const leafNodes = this.currentResultLeafNodes;
+      if (leafNodes.length === 0) return;
+
+      const newCheckedKeys = new Set(this.checkedKeysSet);
+
+      if (this.isAllCurrentResultsSelected) {
+        // 取消全选:移除当前搜索结果中的所有叶子节点
+        leafNodes.forEach(node => {
+          if (!this.checkStrictly) {
+            // 如果不是严格模式,需要处理父子节点关系
+            this.handleCheckboxChange(node, false);
+          } else {
+            // 严格模式下直接移除
+            newCheckedKeys.delete(node.key);
+          }
+        });
+
+        if (this.checkStrictly) {
+          this.checkedKeysSet = newCheckedKeys;
+          this.emitChange();
+        }
+      } else {
+        // 全选:选择当前搜索结果中的所有叶子节点
+        if (!this.multiple) {
+          // 单选模式下,只选择第一个叶子节点
+          const firstLeaf = leafNodes[0];
+          if (firstLeaf) {
+            this.handleCheckboxChange(firstLeaf, true);
+          }
+        } else {
+          // 多选模式下,选择所有叶子节点
+          leafNodes.forEach(node => {
+            if (!this.checkStrictly) {
+              // 如果不是严格模式,需要处理父子节点关系
+              if (!this.checkedKeysSet.has(node.key)) {
+                this.handleCheckboxChange(node, true);
+              }
+            } else {
+              // 严格模式下直接添加
+              newCheckedKeys.add(node.key);
+            }
+          });
+
+          if (this.checkStrictly) {
+            this.checkedKeysSet = newCheckedKeys;
+            this.emitChange();
+          }
+        }
+      }
+    },
+    removeTag(nodeToRemove) {
+      if (this.disabled || this.checkboxDisabled(nodeToRemove)) return;
+      // nodeToRemove here will be a leaf node because displaySelectedNodes only gives leaf nodes
+      this.handleCheckboxChange(nodeToRemove, false);
+    },
+    onPopoverShow() {
+      if (this.$refs.popover && this.$refs.popover.$refs.reference) {
+        this.triggerElRect = this.$refs.popover.$refs.reference.getBoundingClientRect();
+      }
+      this.$nextTick(() => {
+        this.resetScroll();
+        if (this.filterable && this.$refs.popover && this.$refs.popover.$el) {
+          const inputEl = this.$refs.popover.$el.querySelector('.filter-input input');
+          if (inputEl) inputEl.focus();
+        }
+      });
+    }
+  },
+  beforeDestroy() {
+    if (this.updateTimer) clearTimeout(this.updateTimer);
+    if (this.scrollRAF) cancelAnimationFrame(this.scrollRAF);
+  }
+};
+</script>
+
+<style scoped>
+.task-select-tree {
+  display: inline-block;
+  position: relative;
+  font-size: 14px;
+}
+
+.select-trigger-wrapper {
+  background-color: #fff;
+  border-radius: 4px;
+  border: 1px solid #dcdfe6;
+  box-sizing: border-box;
+  color: #606266;
+  display: flex;
+  align-items: center;
+  min-height: 32px; /* Element UI default input height */
+  padding: 0 30px 0 10px;
+  position: relative;
+  transition: border-color .2s cubic-bezier(.645,.045,.355,1);
+  width: 100%;
+  cursor: pointer;
+  overflow: hidden; /* To contain tags */
+}
+
+.select-trigger-wrapper.is-disabled {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #c0c4cc;
+  cursor: not-allowed;
+}
+
+.select-trigger-wrapper:hover:not(.is-disabled) {
+  border-color: #c0c4cc;
+}
+
+.select-trigger-wrapper.is-active:not(.is-disabled) {
+  border-color: #409eff;
+}
+
+.tags-or-value-container {
+  flex-grow: 1;
+  display: flex;
+  flex-wrap: wrap; /* Allow tags to wrap */
+  gap: 4px; /* Space between tags */
+  align-items: center;
+  overflow: hidden; /* Hide overflowed tags/text */
+  padding: 2px 0; /* Minimal padding for tags */
+  min-height: 28px; /* Ensure container has some height */
+}
+
+
+.single-value-display,
+.placeholder-text {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  line-height: 28px; /* Align with tags/input field */
+}
+
+.single-value-display {
+  color: #606266;
+}
+
+.placeholder-text {
+  color: #c0c4cc;
+}
+
+.icons-container {
+  position: absolute;
+  right: 8px;
+  top: 50%;
+  transform: translateY(-50%);
+  display: flex;
+  align-items: center;
+  color: #c0c4cc;
+  height: 100%;
+}
+
+.clear-icon {
+  cursor: pointer;
+  margin-right: 5px;
+  display: none; /* Hidden by default */
+  font-size: 14px;
+}
+
+.select-trigger-wrapper:hover .clear-icon {
+  display: inline-block; /* Show on hover */
+}
+.select-trigger-wrapper.is-disabled .clear-icon {
+  display: none !important; /* Never show if component is disabled */
+}
+
+
+.arrow-icon {
+  transition: transform .3s;
+  font-size: 14px;
+}
+
+.arrow-icon.is-reverse {
+  transform: rotate(180deg);
+}
+
+.tree-container {
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  background-color: #fff;
+  margin-top: 8px; /* If filter input is present */
+}
+
+.virtual-tree-list {
+  position: relative; /* For absolute positioning of virtual-content if needed */
+}
+
+.virtual-content {
+  width: 100%; /* Ensure full width */
+}
+
+.virtual-tree-node {
+  box-sizing: border-box;
+  cursor: pointer;
+  user-select: none;
+  border-bottom: 1px solid #f5f7fa; /* Lighter border */
+  transition: background-color 0.2s;
+  min-height: 36px; /* Ensure consistency with itemHeight */
+}
+.virtual-tree-node:last-child {
+  border-bottom: none;
+}
+
+.virtual-tree-node:hover:not(.is-disabled) {
+  background-color: #f5f7fa;
+}
+
+.virtual-tree-node.is-checked:not(.is-disabled) {
+  /* color: #409eff; */
+}
+.virtual-tree-node.is-checked .node-label {
+  /* font-weight: bold; */
+}
+
+
+.virtual-tree-node.is-disabled {
+  color: #c0c4cc;
+  cursor: not-allowed;
+  background-color: #f5f7fa;
+}
+.virtual-tree-node.is-disabled .node-label {
+  color: #c0c4cc;
+}
+
+
+.node-expand-container {
+  width: 20px;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+
+.node-expand-icon {
+  cursor: pointer;
+  color: #c0c4cc;
+  transition: transform 0.2s, color 0.2s;
+  font-size: 12px;
+}
+
+.node-expand-icon:hover {
+  color: #409eff;
+}
+
+.node-expand-placeholder {
+  width: 12px;
+  height: 12px;
+  display: block;
+}
+
+
+.node-checkbox {
+  margin: 0 8px 0 4px;
+  flex-shrink: 0;
+}
+
+.node-label {
+  flex: 1;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  line-height: 1.4;
+  font-weight: normal;
+  padding-right: 5px;
+}
+
+.node-detail-text {
+  font-size: 12px;
+  color: #909399;
+  margin-left: 8px;
+  flex-shrink: 0;
+  white-space: nowrap;
+}
+
+.popover-content-wrapper {
+  max-width: 100%;
+}
+
+.popover-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-top: 10px;
+  border-top: 1px solid #ebeef5;
+  margin-top: 10px;
+}
+
+.selected-count {
+  font-size: 12px;
+  color: #909399;
+}
+
+.filter-input {
+  margin-bottom: 8px;
+}
+
+.trigger-tag {
+  margin: 1px 0;
+  max-width: 150px;
+}
+
+.trigger-tag >>> .el-tag__content {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+</style>
+
+<style>
+/* Global styles for popper, if needed */
+.task-select-tree-popper.el-popover {
+  padding: 12px;
+}
+
+.task-select-tree-popper .el-checkbox__input.is-disabled .el-checkbox__inner {
+  background-color: #edf2fc;
+  border-color: #dcdfe6;
+  cursor: not-allowed;
+}
+
+.task-select-tree-popper .el-checkbox__input.is-checked .el-checkbox__inner {
+  background-color: #409eff;
+  border-color: #409eff;
+}
+.task-select-tree-popper .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner {
+  background-color: #f2f6fc;
+  border-color: #dcdfe6;
+}
+</style>

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

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

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

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

+ 2 - 1
src/main.js

@@ -10,7 +10,7 @@ import App from './App'
 import store from './store'
 import router from './router'
 import directive from './directive' //directive
-
+import elementDirective from './directive/select'
 import './assets/icons' // icon
 import './permission' // permission control
 import { getDicts } from "@/api/system/dict/data";
@@ -75,6 +75,7 @@ Vue.component('H5Editor', H5Editor)
 
 Vue.use(directive)
 Vue.use(VueMeta)
+Vue.use(elementDirective)
 
 /**
  * If you don't want to use mock-server

+ 25 - 0
src/router/index.js

@@ -263,6 +263,31 @@ export const constantRoutes = [
       }
     ]
   },
+  {
+    path: '/his/aiWorkflow',
+  component: () => import('@/layout/index'),
+  hidden: true,
+  children: [
+    {
+      path: '',
+      component: () => import('@/views/his/aiWorkflow/index'),
+      name: 'AiWorkflow',
+      meta: { title: 'AI工作流', icon: 'workflow' }
+    },
+    {
+      path: 'design',
+      component: () => import('@/views/his/aiWorkflow/design'),
+      name: 'AiWorkflowDesign',
+      meta: { title: '工作流设计', activeMenu: '/his/aiWorkflow' }
+    },
+    {
+      path: 'design/:id',
+      component: () => import('@/views/his/aiWorkflow/design'),
+      name: 'AiWorkflowEdit',
+      meta: { title: '编辑工作流', activeMenu: '/his/aiWorkflow' }
+    }
+  ]
+  },
 ]
 
 export default new Router({

+ 1 - 0
src/utils/cos.js

@@ -39,6 +39,7 @@ export const uploadObject = async (file, onProgress, type, callBackUp) => {
     })
 
     console.log("初始化成功")
+    console.log(process.env)
     const fileName = file.name || ""
     const upload_file_name = new Date().getTime() + "." + fileName.split(".")[fileName.split(".").length - 1]
     const date = new Date()

+ 42 - 9
src/views/company/companyMoneyLogs/index.vue

@@ -24,7 +24,7 @@
         </el-select>
       </el-form-item>
 
-      <el-form-item label="商城订单号" label-width="100px" prop="orderCode" v-if="queryParams.logsType==3 || queryParams.logsType==4 || queryParams.logsType==5 || queryParams.logsType==6" >
+      <el-form-item label="订单号" label-width="100px" prop="orderCode" v-if="queryParams.logsType==3 || queryParams.logsType==4 || queryParams.logsType==5 || queryParams.logsType==6" >
           <el-input
             v-model="queryParams.orderCode"
             placeholder="请输入商城订单号"
@@ -87,9 +87,17 @@
           icon="el-icon-download"
           size="mini"
 		   :loading="exportLoading"
-          @click="handleExport(1)"
+          @click="handleExport(1, 0)"
           v-hasPermi="['company:companyMoneyLogs:export1']"
         >导出商城订单明细</el-button>
+        <el-button
+          type="warning"
+          icon="el-icon-download"
+          size="mini"
+		   :loading="exportLoading"
+          @click="handleExport(1, 1)"
+          v-hasPermi="['company:companyMoneyLogs:export1']"
+        >导出直播订单明细</el-button>
 
         <!-- <el-button
           type="warning"
@@ -109,7 +117,7 @@
       <el-table-column label="金额" align="center" prop="money" />
       <el-table-column label="余额" align="center" prop="balance" />
 
-      <el-table-column label="商城订单号" align="center" prop="orderCode"  v-if="queryParams.logsType==3 || queryParams.logsType==4 ||queryParams.logsType==5 || queryParams.logsType==6"  />
+      <el-table-column label="订单号" align="center" prop="orderCode"  v-if="queryParams.logsType==3 || queryParams.logsType==4 ||queryParams.logsType==5 || queryParams.logsType==6"  />
       <el-table-column label="支付类型" align="center" prop="payTypeCode"  v-if="queryParams.logsType==3 || queryParams.logsType==4 ||queryParams.logsType==5 || queryParams.logsType==6 " />
 
       <el-table-column label="备注" align="center" prop="remark" />
@@ -168,6 +176,13 @@
       >
       <storeOrderDetails  ref="order" />
     </el-drawer>
+    <el-drawer
+      :with-header="false"
+      size="75%"
+      :title="liveShow.title" :visible.sync="liveShow.open"
+      >
+      <liveOrderDetails  ref="liveOrder" />
+    </el-drawer>
     <el-drawer :with-header="false" size="75%" :title="inquiryShow.title" :visible.sync="inquiryShow.open">
      <inquiryOrderDetails  ref="Details" />
    </el-drawer>
@@ -179,9 +194,10 @@ import { listCompanyMoneyLogs, getCompanyMoneyLogs, delCompanyMoneyLogs, addComp
 import { getCompanyList } from "@/api/company/company";
 import { getTask } from "@/api/common";
 import storeOrderDetails  from '../../components/his/storeOrderDetails.vue';
+import liveOrderDetails from '../../live/liveOrder/liveOrderDetails.vue';
 import inquiryOrderDetails from '../../components/his/inquiryOrderDetails.vue';
 export default {
-  components: { storeOrderDetails,inquiryOrderDetails},
+  components: { storeOrderDetails, liveOrderDetails, inquiryOrderDetails},
   name: "CompanyMoneyLogs",
   data() {
     return {
@@ -192,6 +208,10 @@ export default {
         open:false,
         title:"订单详情"
       },
+      liveShow:{
+        open:false,
+        title:"直播订单详情"
+      },
       inquiryShow:{
         open:false,
         title:"订单详情"
@@ -249,13 +269,23 @@ export default {
   },
   methods: {
     handleDetails(row){
-
       const orderId = row.businessId ;
+      // 根据type字段区分商城订单和直播订单
       if(row.logsType==3||row.logsType==4||row.logsType==5||row.logsType==6 ||row.logsType==13||row.logsType==14){
-        this.show.open=true;
+        // type=0为商城订单,type=1为直播订单
+        if(row.type == 1){
+          // 直播订单
+          this.liveShow.open=true;
+          setTimeout(() => {
+            this.$refs.liveOrder.getDetails(orderId);
+          }, 500);
+        } else {
+          // 商城订单
+          this.show.open=true;
           setTimeout(() => {
-          this.$refs.order.getDetails(orderId);
-        }, 500);
+            this.$refs.order.getDetails(orderId);
+          }, 500);
+        }
       }
       if(row.logsType==12){
         this.inquiryShow.open = true;
@@ -375,7 +405,7 @@ export default {
         }).catch(function() {});
     },
     /** 导出按钮操作 */
-    handleExport(type) {
+    handleExport(type, orderType) {
 		var that=this;
       if(this.createTimeRange!=null&&this.createTimeRange.length==2){
         this.queryParams.createTimeRange=this.createTimeRange[0]+"--"+this.createTimeRange[1]
@@ -385,6 +415,9 @@ export default {
       }
       const queryParams=this.addDateRange(this.queryParams, this.dateRange)
       queryParams.type=type;
+      if(orderType !== undefined){
+        queryParams.orderType=orderType;
+      }
       this.$confirm('是否确认导出账户记录数据项?', "警告", {
           confirmButtonText: "确定",
           cancelButtonText: "取消",

+ 17 - 7
src/views/company/companyUser/indexAll.vue

@@ -80,6 +80,7 @@
               size="mini"
               :disabled="multiple"
               @click="handerCompanyUserAreaList"
+              v-hasPermi="['company:user:updateCompanyUserAreaList']"
             >批量设置销售所属区域</el-button>
           </el-col>
           <el-col :span="1.5">
@@ -89,6 +90,7 @@
               size="mini"
               :disabled="multiple"
               @click="handleSetRegister"
+              v-hasPermi="['company:user:setRegister']"
             >设置单独注册会员</el-button>
           </el-col>
           <el-col :span="1.5">
@@ -98,6 +100,7 @@
               size="mini"
               :disabled="multiple"
               @click="handleAllowedAllRegister"
+              v-hasPermi="['company:user:allowedAllRegister']"
             >允许注册会员开关</el-button>
           </el-col>
           <el-col :span="1.5">
@@ -106,6 +109,7 @@
               plain
               size="mini"
               @click="handleBindCompanyUserCode"
+              v-hasPermi="['company:user:addCodeUrl']"
             >生成注册/绑定销售二维码</el-button>
           </el-col>
           <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -207,6 +211,7 @@
                 plain
                 v-if="scope.row.doctorId!=null"
                 @click="handleUpdateDoctor(scope.row)"
+                v-hasPermi="['qw:companyUser:unBindDoctorId']"
               >换绑医生</el-button>
               <el-button
                 size="mini"
@@ -215,6 +220,7 @@
                 icon="el-icon-link"
                 v-else
                 @click="handleUpdateDoctor(scope.row)"
+                v-hasPermi="['qw:companyUser:unBindDoctorId']"
               >绑定医生</el-button>
               <el-button
                 size="mini"
@@ -223,6 +229,7 @@
                 plain
                 v-if="scope.row.doctorId!=null"
                 @click="handleUnBindUserId(scope.row)"
+                v-hasPermi="['qw:companyUser:unBindDoctorId']"
               >解绑医生</el-button>
               <el-button
                 v-if="scope.row.qwStatus == 0"
@@ -304,7 +311,7 @@
         <el-form-item label="企微公司" prop="corpId">
           <el-select v-model="synform.corpId" placeholder="企微公司"  >
             <el-option
-              v-for="dict in myQwCompanyList"
+              v-for="dict in qwCompanyList"
               :key="dict.dictValue"
               :label="dict.dictLabel"
               :value="dict.dictValue"
@@ -323,7 +330,7 @@
         <el-form-item label="企微公司" prop="corpId">
           <el-select v-model="synNameform.corpId" placeholder="企微公司"  >
             <el-option
-              v-for="dict in myQwCompanyList"
+              v-for="dict in qwCompanyList"
               :key="dict.dictValue"
               :label="dict.dictLabel"
               :value="dict.dictValue"
@@ -476,7 +483,7 @@
             <el-form-item label="企微主体" prop="corpId">
               <el-select v-model="formBindCompany.corpId" placeholder="企微主体" size="small">
                 <el-option
-                  v-for="dict in myQwCompanyList"
+                  v-for="dict in qwCompanyList"
                   :key="dict.dictValue"
                   :label="dict.dictLabel"
                   :value="dict.dictValue"
@@ -678,12 +685,12 @@ import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
 import {bindQwUser, addQwUser, getQwUserByIds,addQwUserName} from '@/api/qw/user';
 import { syncDept } from '@/api/qw/qwDept';
-import {getMyQwCompanyList } from "@/api/qw/user";
 import  selectUser  from "@/views/company/components/selectQwUser.vue";
 import { getConfigByKey } from "@/api/company/companyConfig";
 import axios from "axios";
 import selectDoctor from "@/views/qw/user/selectDoctor.vue";
 import { getCompanyList } from '@/api/company/company'
+import { getQwCompanyList } from '../../../api/qw/user'
 export default {
   name: "User",
   components: {selectDoctor, Treeselect ,selectUser},
@@ -707,7 +714,7 @@ export default {
       loading: false,
       qwUserList:[],
       qwUserId:[],
-      myQwCompanyList:[],
+      qwCompanyList:[],
       qwUser:[],
       user:{
         open:false,
@@ -921,8 +928,8 @@ export default {
     getCitysAreaList().then(res=>{
       this.citysAreaList=res.data;
     })
-    getMyQwCompanyList().then(response => {
-      this.myQwCompanyList = response.data;
+    getQwCompanyList().then(response => {
+      this.qwCompanyList = response.data;
     });
   },
   methods: {
@@ -1168,6 +1175,7 @@ export default {
         this.open = true;
         this.title = "添加员工";
         this.form.password = this.form1.loginPassword;
+        this.form.companyId = this.companyIdOpen.companyId;
       });
     },
 
@@ -1276,6 +1284,7 @@ export default {
               if (response.code === 200) {
                 this.msgSuccess("修改成功");
                 this.open = false;
+                this.companyIdOpen.open=false;
                 this.getList();
               }
             });
@@ -1284,6 +1293,7 @@ export default {
               if (response.code === 200) {
                 this.msgSuccess("新增成功");
                 this.open = false;
+                this.companyIdOpen.open=false;
                 this.getList();
               }
             });

+ 245 - 38
src/views/components/course/userCourseCatalogDetails.vue

@@ -8,6 +8,10 @@
         <el-input v-model="queryParams.title" placeholder="请输入小节名称" clearable size="small"
                   @keyup.enter.native="handleQuery"/>
       </el-form-item>
+      <el-form-item label="小节id" prop="videoId">
+        <el-input v-model="queryParams.videoId" placeholder="请输入小节id" clearable size="small"
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
       <el-form-item label="排序" prop="sorting" clearable size="small">
         <el-select v-model="queryParams.sorting" >
           <el-option label="升序" value="1" />
@@ -55,27 +59,41 @@
         </el-button>
       </el-col>
       <el-col :span="1.5">
-        <el-button 
-          type="primary" 
-          plain 
-          icon="el-icon-delete" 
-          size="mini" 
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-delete"
+          size="mini"
           @click="handleSync"
           v-hasPermi="['course:userCourseVideo:sync']"
         >同步模板数据
         </el-button>
       </el-col>
       <el-col :span="1.5">
-        <el-button 
-          type="primary" 
-          icon="el-icon-refresh" 
-          size="mini" 
+        <el-button
+          type="primary"
+          icon="el-icon-refresh"
+          size="mini"
           :loading="syncPeriodLoading"
           :disabled="syncPeriodLoading"
           @click="handleSyncCoursePeriod"
         >{{ syncPeriodLoading ? '同步中...' : '同步会员营期' }}
         </el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-edit" size="mini" :disabled="multiple" @click="handleDown"
+                   v-hasPermi="['course:userCourseVideo:batchDown']">批量下架
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-edit" size="mini" :disabled="multiple" @click="handleUp"
+                   v-hasPermi="['course:userCourseVideo:batchUp']">批量上架</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-edit" size="mini" :disabled="multiple" @click="handleEditCover"
+                   v-hasPermi="['course:userCourseVideo:batchEditCover']">批量修改封面图
+        </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">
@@ -168,6 +186,44 @@
                       @change="handleVideoChange" @selectProjects="handleSelectProjects" ref="videoUpload"
                       append-to-body/>
 
+        <el-form-item label="关联疗法" >
+          <el-button size="small" type="primary" @click="choosePackage">选取疗法</el-button>
+          <el-table border width="100%" style="margin-top:5px;"  :data="packageList">
+            <el-table-column label="疗法名称" align="center" prop="packageName"/>
+            <el-table-column label="疗法图片" align="center" prop="imgUrl">
+              <template slot-scope="scope">
+                <img :src="scope.row.imgUrl" style="height: 80px;">
+              </template>
+            </el-table-column>
+            <el-table-column label="疗法别名" align="center" prop="secondName"/>
+            <el-table-column label="总金额" align="center" prop="totalPrice"/>
+            <!-- 根据课程类型控制是否显示弹出时间列:0是公域(显示),1是私域(不显示) -->
+            <el-table-column label="弹出时间" align="center" width="250px" v-if="isPrivate == 0">
+              <template slot-scope="scope">
+                <div>
+                  <el-time-select
+                    v-model="scope.row.duration"
+                    size="mini"
+                    placeholder="选择时间"
+                    :picker-options="getPickerOptions()"
+                    @change="handleTimeChange(scope.$index, scope.row)"
+                  ></el-time-select>
+                </div>
+              </template>
+            </el-table-column>
+            <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="handlePackageDelete(scope.row)"
+                >删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-form-item >
+
         <el-form-item label="课题选择" prop="questionBankId">
           <el-button size="small" type="primary" @click="chooseQuestionBank">选取课题</el-button>
           <el-table border width="100%" style="margin-top:5px;" :data="form.questionBankList">
@@ -496,10 +552,10 @@
     </el-dialog>
 
     <!-- 同步结果对话框 -->
-    <el-dialog 
-      :title="syncResultDialog.title" 
-      :visible.sync="syncResultDialog.open" 
-      width="700px" 
+    <el-dialog
+      :title="syncResultDialog.title"
+      :visible.sync="syncResultDialog.open"
+      width="700px"
       append-to-body
       class="sync-result-dialog"
     >
@@ -516,9 +572,9 @@
         <!-- 营期详情 -->
         <div class="period-details">
           <el-divider content-position="left">营期明细</el-divider>
-          <el-table 
-            :data="syncResultDialog.data.periodDetails" 
-            border 
+          <el-table
+            :data="syncResultDialog.data.periodDetails"
+            border
             style="width: 100%"
             max-height="400"
           >
@@ -535,12 +591,35 @@
           </el-table>
         </div>
       </div>
-      
+
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="closeSyncResultDialog">确 定</el-button>
       </div>
     </el-dialog>
 
+    <!-- 批量修改封面 -->
+    <el-dialog :title="batchEditCoverDialog.title" :visible.sync="batchEditCoverDialog.visible" width="500px" append-to-body>
+      <el-form ref="batchEditCoverDialogForm"
+               :model="batchEditCoverDialog.form"
+               :rules="batchEditCoverDialog.rules"
+               v-loading="batchEditCoverDialog.uploadLoading">
+        <el-form-item label="视频封面" prop="thumbnail">
+          <el-upload v-model="batchEditCoverDialog.form.thumbnail"
+                     class="avatar-uploader"
+                     :action="uploadUrl"
+                     :show-file-list="false"
+                     :on-success="handleCoverSuccess"
+                     :before-upload="beforeAvatarUpload">
+            <img v-if="batchEditCoverDialog.form.thumbnail" :src="batchEditCoverDialog.form.thumbnail" class="avatar" width="300px">
+            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
+          </el-upload>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitEditCoverForm">确 定</el-button>
+        <el-button @click="cancelEditCoverForm">取 消</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
@@ -558,9 +637,10 @@ import {
   sortCourseVideo,
   updates,
   updateUserCourseVideo,
-  syncTemplate,
+  syncTemplate, batchDownUserCourseVideo, batchEditCover, batchUpUserCourseVideo,
   syncCoursePeriod
-} from "@/api/course/userCourseVideo";
+} from '@/api/course/userCourseVideo'
+// import {syncTemplate} from '@/api/course/userCourse'
 import QuestionBank from "@/views/course/courseQuestionBank/QuestionBank.vue";
 import CourseProduct from "@/views/course/fsCourseProduct/CourseProduct.vue";
 import VideoUpload from "@/components/VideoUpload/index.vue";
@@ -679,6 +759,7 @@ export default {
         courseId: null,
         title: null,
         sorting:"1",
+        videoId: null,
       },
       addBatchData: {
         open: false,
@@ -745,7 +826,21 @@ export default {
         videoId: null,
         title: ""
       },
-      enableRandomRedPacket:false
+      enableRandomRedPacket:false,
+      // 批量修改封面
+      batchEditCoverDialog: {
+        title: '修改视频封面',
+        visible: false,
+        uploadLoading: false,
+        form: {
+          thumbnail: null,
+        },
+        rules: {
+          thumbnail: [
+            {required: true, message: "视频封面不能为空", trigger: "change"}
+          ],
+        }
+      }
     }
   },
   created() {
@@ -778,6 +873,22 @@ export default {
           .padStart(2, "0")}`, // 动态结束时间
       };
     },
+
+    // 处理时间选择框值变化
+    handleTimeChange(index, row) {
+      // 确保 packageList 中的数据被正确更新
+      this.$set(this.packageList, index, row);
+      // 同步更新 form.packageJson 字段
+      this.$nextTick(() => {
+        // 确保每个疗法包都有 duration 字段
+        this.packageList.forEach(item => {
+          if (item.duration === undefined || item.duration === null) {
+            item.duration = ''; // 空值应初始化为空字符串而不是null,避免显示"null"
+          }
+        });
+        this.form.packageJson = JSON.stringify(this.packageList);
+      });
+    },
     handlePackageDelete(row) {
       this.packageList.splice(this.packageList.findIndex(item => item.packageId === row.packageId), 1)
     },
@@ -1533,12 +1644,12 @@ export default {
             if (response.code === 200) {
               // 成功提示
               this.$message.success(response.msg)
-              
+
               // 如果有详细数据,显示详情对话框
               if (response.data && response.data.periodDetails) {
                 this.showSyncResult(response.data)
               }
-              
+
               // 刷新列表
               this.getList()
             } else {
@@ -1628,29 +1739,125 @@ export default {
     const index = rule.index;
     const maxAmount = this.form.randomRedPacketRulesArr[index].maxAmount;
 
-    if (value > maxAmount) {
-      callback(new Error("最小金额不能大于最大金额"));
-    } else {
-      callback();
-    }
-  },
-  validateRules() {
-    this.form.randomRedPacketRulesArr.forEach((rule) => {
-      if (rule.minAmount === undefined || rule.minAmount < 0.01) {
-        rule.minAmount = 0.01;
+      if (value > maxAmount) {
+        callback(new Error("最小金额不能大于最大金额"));
+      } else {
+        callback();
       }
-      if (rule.maxAmount === undefined || rule.maxAmount < rule.minAmount) {
-        rule.maxAmount = rule.minAmount;
+    },
+      validateRules() {
+      this.form.randomRedPacketRulesArr.forEach((rule) => {
+        if (rule.minAmount === undefined || rule.minAmount < 0.01) {
+          rule.minAmount = 0.01;
+        }
+        if (rule.maxAmount === undefined || rule.maxAmount < rule.minAmount) {
+          rule.maxAmount = rule.minAmount;
+        }
+        if (rule.weight === undefined || rule.weight < 1) {
+          rule.weight = 1;
+        }
+      });
+    },
+    /** 下架 **/
+    handleDown() {
+      const videoIds = this.ids;
+      this.$confirm('是否确认下架视频编号为"' + videoIds + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return batchDownUserCourseVideo(videoIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("下架成功");
+      }).catch(() => {
+      });
+    },
+    /** 上架按钮操作 */
+    handleUp() {
+      const videoIds = this.ids;
+      this.$confirm('是否确认上架视频编号为"' + videoIds + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return batchUpUserCourseVideo(videoIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("上架成功");
+      }).catch(function () {
+      });
+    },
+    /** 修改封面 **/
+    handleEditCover() {
+      this.batchEditCoverDialog.form = {
+        thumbnail: null
       }
-      if (rule.weight === undefined || rule.weight < 1) {
-        rule.weight = 1;
+      this.batchEditCoverDialog.visible = true
+    },
+    handleCoverSuccess(res, file) {
+      if (res.code === 200) {
+        this.batchEditCoverDialog.form.thumbnail = res.url;
+        this.$forceUpdate()
+      } else {
+        this.msgError(res.msg);
       }
-    });
-  },
+    },
+    submitEditCoverForm() {
+      this.$refs["batchEditCoverDialogForm"].validate(valid => {
+        if (!valid) {
+          return;
+        }
+
+        const thumbnail = this.batchEditCoverDialog.form.thumbnail
+        const videoIds = this.ids
+
+        if (!thumbnail || thumbnail === '') {
+          this.$message({
+            message: '请上传封面!',
+            type: 'warning'
+          });
+          return
+        }
+
+        if (!videoIds || videoIds.length === 0) {
+          this.$message({
+            message: '请选择小节!',
+            type: 'warning'
+          });
+          return
+        }
+
+        const params = {
+          thumbnail: thumbnail,
+          videoIds: videoIds
+        }
+
+        batchEditCover(params).then(response => {
+          this.msgSuccess("修改成功")
+          this.batchEditCoverDialog.visible = false
+          this.getList();
+        });
+      });
+    },
+    cancelEditCoverForm() {
+      this.batchEditCoverDialog.visible = false
+      this.batchEditCoverDialog.form = {
+        thumbnail: null,
+      }
+    },
+
   }
 }
 </script>
 <style scoped>
+.avatar-uploader-icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+</style>
+<style scoped>
 .image-size-tip {
   margin-top: 5px;
   font-size: 12px;

+ 40 - 35
src/views/components/his/integralOrderDetails.vue

@@ -93,6 +93,11 @@
             <p>{{ scope.row.goodsName }}</p>
           </template>
         </el-table-column>
+        <el-table-column label="数量" align="center">
+          <template slot-scope="scope">
+            <p>{{ scope.row.num }}</p>
+          </template>
+        </el-table-column>
         <el-table-column label="积分" align="center">
           <template slot-scope="scope">
             <p>¥{{ scope.row.integral }}</p>
@@ -106,7 +111,7 @@
 
       </el-table>
     </div>
-    
+
     <!-- 操作记录 -->
     <div class="contentx" v-if="item != null" style="padding-bottom: 70px;">
       <div class="desct">
@@ -124,7 +129,7 @@
         </el-table-column>
         <!-- <el-table-column prop="operator" label="操作员" align="center" width="120">
         </el-table-column> -->
-        
+
       </el-table>
     </div>
     <el-dialog width="35%" title="发货" :visible.sync="sendVisible" append-to-body @close="sendCancel">
@@ -173,7 +178,7 @@
 
             </el-table>
 
-            
+
             <el-pagination small layout="prev, pager, next" :total="total" :page-size="queryParams.pageSize"
               :current-page="queryParams.pageNum" @current-change="handlePageChange"
               style="margin-top: 10px; text-align: center;">
@@ -183,7 +188,7 @@
               提示:点击表格行选择代服账号
             </div>
 
-            
+
             <div v-if="selectedRow" style="margin-top: 10px; padding: 8px; background: #f5f7fa; border-radius: 4px;">
               <span style="color: #67C23A;">已选择:</span>
               <span>{{ selectedRow.loginAccount }} ({{ selectedRow.senderName }})</span>
@@ -232,9 +237,9 @@
         </el-form-item>
         <el-form-item label="收货地址" required v-if="item.status == 1">
           <div style="margin-bottom: 10px;">
-            <el-select 
-              v-model="selectedProvince" 
-              placeholder="请选择省" 
+            <el-select
+              v-model="selectedProvince"
+              placeholder="请选择省"
               style="width: 32%; margin-right: 1%"
               @change="onProvinceChange"
               clearable
@@ -246,10 +251,10 @@
                 :value="item"
               />
             </el-select>
-            
-            <el-select 
-              v-model="selectedCity" 
-              placeholder="请选择市" 
+
+            <el-select
+              v-model="selectedCity"
+              placeholder="请选择市"
               style="width: 32%; margin-right: 1%"
               @change="onCityChange"
               :disabled="!selectedProvince"
@@ -262,10 +267,10 @@
                 :value="item"
               />
             </el-select>
-            
-            <el-select 
-              v-model="selectedDistrict" 
-              placeholder="请选择区" 
+
+            <el-select
+              v-model="selectedDistrict"
+              placeholder="请选择区"
               style="width: 32%;"
               @change="onDistrictChange"
               :disabled="!selectedCity"
@@ -279,10 +284,10 @@
               />
             </el-select>
           </div>
-          
-          <el-input 
-            v-model="detailAddress" 
-            placeholder="请输入详细地址(街道、门牌号等)" 
+
+          <el-input
+            v-model="detailAddress"
+            placeholder="请输入详细地址(街道、门牌号等)"
             style="width: 100%"
             clearable
           />
@@ -290,9 +295,9 @@
 
         <!-- 其他状态显示只读的地址信息 -->
         <el-form-item label="收货地址" v-else>
-          <el-input 
-            v-model="editForm.userAddress" 
-            placeholder="请输入" 
+          <el-input
+            v-model="editForm.userAddress"
+            placeholder="请输入"
             disabled
           />
         </el-form-item>
@@ -599,7 +604,7 @@ export default {
         try {
           const response = await getCitys(); // 假设getCitys是您导入的方法
           this.provinceOptions = response.data || [];
-          
+
           // 如果现有地址不为空,尝试解析并设置初始值
           if (this.item.userAddress) {
             this.parseExistingAddress(this.item.userAddress);
@@ -613,7 +618,7 @@ export default {
     // 解析现有地址
     parseExistingAddress(address) {
       if (!address || this.item.status != 1) return;
-      
+
       // 假设地址格式为 "省 市 区 详细地址"
       const parts = address.split(' ');
       if (parts.length >= 4) {
@@ -621,19 +626,19 @@ export default {
         const cityName = parts[1];
         const districtName = parts[2];
         this.detailAddress = parts.slice(3).join(' '); // 剩余部分作为详细地址
-        
+
         // 设置省份
         const province = this.provinceOptions.find(p => p.label === provinceName);
         if (province) {
           this.selectedProvince = province;
           this.onProvinceChange(province);
-          
+
           // 设置城市
           const city = province.children?.find(c => c.label === cityName);
           if (city) {
             this.selectedCity = city;
             this.onCityChange(city);
-            
+
             // 设置区域
             const district = city.children?.find(d => d.label === districtName);
             if (district) {
@@ -696,14 +701,14 @@ export default {
         const city = this.selectedCity ? this.selectedCity.label : '';
         const district = this.selectedDistrict ? this.selectedDistrict.label : '';
         const detail = this.detailAddress || '';
-        
+
         // 使用空格拼接四个字段
         this.editForm.userAddress = `${province} ${city} ${district} ${detail}`.trim();
       } else {
         // 其他状态保持原地址不变
         this.editForm.userAddress = this.item.userAddress;
       }
-      
+
       this.$refs["editForm"].validate(valid => {
         if (valid) {
           updateIntegralOrder(this.editForm).then(response => {
@@ -782,10 +787,10 @@ export default {
     },
     sendGoods() {
       // 手动验证所有必填字段
-      if (!this.selectedRow) {
-        this.msgError('请选择代服账号');
-        return;
-      }
+      // if (!this.selectedRow) {
+      //   this.msgError('请选择代服账号');
+      //   return;
+      // }
 
       if (!this.form.deliveryName || !this.form.deliveryCode) {
         this.msgError('请选择快递公司');
@@ -912,7 +917,7 @@ export default {
         });
       });
     },
-    
+
     // 执行完成订单请求
     executeFinishOrder() {
       const orderCode = this.item.orderCode;
@@ -939,7 +944,7 @@ export default {
       if (!this.item || !this.item.orderId) {
         return;
       }
-      
+
       this.logsLoading = true;
       getOrderLogs(this.item.orderId).then(response => {
         if (response.code === 200) {

+ 18 - 4
src/views/components/his/userDetails.vue

@@ -101,7 +101,7 @@
 
 
 
-  <div class="contentx" v-if="item!=null">
+  <div class="contentx" v-if="item!=null && fsUserInfo !== 'gzzdy'">
         <div class="desct">
           <span v-if="patientInfo">
             {{ patientInfo }}
@@ -121,19 +121,26 @@
  <userAddDetails  ref="userAddDetail" />
 
    </div>
-  <div class="contentx" v-if="item!=null" >
+  <div class="contentx" v-if="item!=null && fsUserInfo !== 'gzzdy'" >
  <div class="desct">
       用户药品订单
  </div>
     <userStorerDetails  ref="userDetails" />
    </div>
-<div class="contentx" v-if="item!=null" >
+<div class="contentx" v-if="item!=null && fsUserInfo !== 'gzzdy'" >
  <div class="desct">
       用户问诊订单
  </div>
     <userInquiryOrderDetails  ref="InquiryDetails" />
    </div>
 
+  <div class="contentx" v-if="item != null && fsUserInfo === 'gzzdy'" >
+    <div class="desct">
+      商城订单
+    </div>
+    <userStoreOrderList  ref="userStoreOrderList" />
+  </div>
+
     <!-- 积分记录 -->
     <div class="contentx" v-if="item!=null" >
       <div class="desct">
@@ -156,10 +163,11 @@ import userPatietDetails from "../his/userPatietDetails.vue";
 import userInquiryOrderDetails from "../his/userInquiryOrderDetails.vue";
 import userAddDetails from "../his/userAddDetails.vue";
     import userIntegralDetails from "../his/userIntegralDetails.vue";
+import userStoreOrderList from "../his/userStoreOrderList.vue";
   export default {
     name: "storedet",
     props:["data"],
-     components: { userStorerDetails ,userInquiryOrderDetails,userPatietDetails,userAddDetails,userIntegralDetails},
+     components: { userStorerDetails ,userInquiryOrderDetails,userPatietDetails,userAddDetails,userIntegralDetails, userStoreOrderList},
     data() {
       return {
         patientInfo: process.env.VUE_APP_PATIENT_INFO,
@@ -183,6 +191,7 @@ import userAddDetails from "../his/userAddDetails.vue";
         actName:"10",
         businessTypeOptions:[],
         couponStatusOptions:[],
+        fsUserInfo: process.env.VUE_APP_FS_USER_INFO
       }
     },
     created() {
@@ -245,6 +254,11 @@ import userAddDetails from "../his/userAddDetails.vue";
               setTimeout(() => {
                   this.$refs.userIntegralDetail.getIntegralLogs(orderId);
               }, 1);
+              setTimeout(() => {
+                if (this.$refs.userStoreOrderList) {
+                  this.$refs.userStoreOrderList.getUserOrderDetails(orderId);
+                }
+              }, 1);
           });
           this.patient=null;
           getPatientByUserId(orderId).then(response => {

+ 275 - 0
src/views/components/his/userStoreOrderList.vue

@@ -0,0 +1,275 @@
+<template>
+  <div class="aacontainer">
+    <el-tabs type="card" v-model="actName" @tab-click="handleClickX">
+      <el-tab-pane label="全部订单" name="10"></el-tab-pane>
+      <el-tab-pane v-for="(item,index) in orderOptions" :label="item.dictLabel" :name="item.dictValue"></el-tab-pane>
+    </el-tabs>
+    <el-table v-loading="loading" :data="storeOrderList">
+      <el-table-column label="订单号" align="center" prop="orderCode" width="180px"/>
+<!--      <el-table-column label="所属店铺" align="center" prop="storeName" width="180px"/>-->
+      <el-table-column label="所属公司" align="center" prop="companyName" width="180px"/>
+      <el-table-column label="所属员工" align="center" prop="companyUserNickName" width="180px"/>
+      <el-table-column label="用户昵称" align="center" prop="userPhone" show-overflow-tooltip width="100px"/>
+      <el-table-column label="收件人" align="center" prop="realName" />
+      <el-table-column label="订单总价" align="center" prop="totalPrice" />
+<!--      <el-table-column label="商品分类" align="center" prop="totalPrice" />-->
+      <el-table-column label="实付金额" align="center" prop="payPrice" />
+      <el-table-column label="代收金额" align="center" prop="payDelivery" />
+      <el-table-column label="订单状态" align="center" prop="status" >
+        <template slot-scope="scope">
+              <dict-tag :options="orderOptions" :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="isPay" >-->
+<!--        <template slot-scope="scope">-->
+<!--              <dict-tag :options="payStatusOptions" :value="scope.row.isPay"/>-->
+<!--            </template>-->
+<!--      </el-table-column>-->
+      <el-table-column label="支付时间" align="center" prop="payTime" width="180" />
+      <el-table-column label="支付方式" align="center" prop="payType" >
+        <template slot-scope="scope">
+              <dict-tag :options="PayOptions" :value="scope.row.payType"/>
+         </template>
+      </el-table-column>
+      <el-table-column label="发货时间" align="center" prop="deliverySendTime" width="180" />
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { getToken } from "@/utils/auth";
+import {listStoreOrder} from "@/api/hisStore/storeOrder";
+export default {
+  name: "userInquir",
+  props:["data"],
+  data() {
+    return {
+      actName:"10",
+      show:{
+              title:"订单详情",
+              open:false,
+            },
+      upload: {
+        // 是否显示弹出层
+        open: false,
+        // 弹出层标题
+        title: "",
+        // 是否禁用上传
+        isUploading: false,
+        // 是否更新已经存在的用户数据
+        updateSupport: 0,
+        // 设置上传的请求头部
+        headers: { Authorization: "Bearer " + getToken() },
+        // 上传的地址
+        // url: process.env.VUE_APP_BASE_API + "/his/order/importData"
+      },
+      // 遮罩层
+      loading: true,
+      // // 导出遮罩层
+      // exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 订单表格数据
+      storeOrderList: [],
+      // 弹出层标题
+      title: "",
+      createTime:null,
+      payTime:null,
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        storeId: null,
+        orderCode: null,
+        userId: null,
+        userName: null,
+        userPhone: null,
+        userAddress: null,
+        cartId: null,
+        totalNum: null,
+        totalPrice: null,
+        payPrice: null,
+        payMoney: null,
+        isPay: null,
+        payTime: null,
+        payType: null,
+        status: null,
+        refundStatus: null,
+        refundImg: null,
+        refundExplain: null,
+        refundTime: null,
+        refundReason: null,
+        refundMoney: null,
+        deliveryCode: null,
+        deliveryName: null,
+        deliverySn: null,
+        isDel: null,
+        costPrice: null,
+        verifyCode: null,
+        shippingType: null,
+        isChannel: null,
+        isPrescribe: null,
+        prescribeId: null,
+        finishTime: null,
+        patientName: null,
+        doctorName: null,
+        sTime:null,
+        eTime:null,
+        paysTime:null,
+        payeTime:null,
+        deliveryTime: null,
+        tuiMoney: null,
+        tuiMoneyStatus: null,
+        tuiUserId: null,
+        orderCreateType: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      },
+       PayOptions:[],
+       orderOptions:[],
+       payStatusOptions:[],
+       refundOptions:[],
+       channelOptions:[],
+       orderTypeOptions:[],
+       tuiOptions:[],
+       orOptions:[],
+       storeOPtions:[],
+    };
+  },
+  created() {
+    this.getDicts("store_pay_type").then(response => {
+        this.PayOptions = response.data;
+      });
+    this.getDicts("store_order_type").then(response => {
+        this.orderTypeOptions = response.data;
+      });
+    this.getDicts("store_order_status").then(response => {
+        this.orderOptions = response.data;
+      });
+
+    this.getDicts("sys_store_payment_status").then(response => {
+        this.payStatusOptions = response.data;
+      });
+    this.getDicts("sys_refund_status").then(response => {
+        this.refundOptions = response.data;
+      });
+    this.getDicts("sys_channel").then(response => {
+        this.channelOptions = response.data;
+      });
+  },
+  methods: {
+    getUserOrderDetails(id){
+      this.queryParams.userId=id
+      this.getList();
+    },
+
+    /** 查询订单列表 */
+    getList() {
+      this.loading = true;
+      listStoreOrder(this.queryParams).then(response => {
+        this.storeOrderList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        orderId: null,
+        storeId: null,
+        orderCode: null,
+        userId: null,
+        userName: null,
+        userPhone: null,
+        userAddress: null,
+        cartId: null,
+        totalNum: null,
+        totalPrice: null,
+        payPrice: null,
+        payMoney: null,
+        isPay: null,
+        payTime: null,
+        payType: null,
+        createTime: null,
+        updateTime: null,
+        status: null,
+        refundStatus: "0",
+        refundImg: null,
+        refundExplain: null,
+        refundTime: null,
+        refundReason: null,
+        refundMoney: null,
+        deliveryCode: null,
+        deliveryName: null,
+        deliverySn: null,
+        remark: null,
+        isDel: null,
+        costPrice: null,
+        verifyCode: null,
+        shippingType: null,
+        isChannel: null,
+        isPrescribe: null,
+        prescribeId: null,
+        finishTime: null,
+        deliveryTime: null,
+        tuiMoney: null,
+        tuiMoneyStatus: 0,
+        tuiUserId: null,
+        orderCreateType: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+   handleClickX(tab, event) {
+    if(tab.name=="10"){
+      this.queryParams.status=null;
+    }else{
+      this.queryParams.status=tab.name;
+    }
+     this.handleQuery();
+   },
+  changeTime(){
+        if(this.createTime!=null){
+          this.queryParams.sTime=this.createTime[0];
+          this.queryParams.eTime=this.createTime[1];
+        }else{
+          this.queryParams.sTime=null;
+          this.queryParams.eTime=null;
+        }
+      },
+  }
+};
+</script>

+ 60 - 4
src/views/components/index/statisticsDashboard.vue

@@ -85,7 +85,7 @@
                 <count-to :start-val="0" :end-val="balance" :duration="3600" class="card-panel-num" />
               </div>
             </div>
-            <div class="property-card propertyline">
+            <div class="property-card propertyline" v-if="projectFrom === 'sczy'">
               <div class="property-title">
                 <i class="el-icon-money"></i>
                 润天余额(元)
@@ -224,6 +224,18 @@
                 0
               </div>
             </div>
+            <div class="internetbox-messge">
+              <div class="internet-card">
+                <img src="@/assets/images/message.png" alt="">
+
+                <span class="internet-title">
+                  微信上传失败订单数量
+                </span>
+              </div>
+              <div class="internet-number">
+                {{ wxFailedCount }}
+              </div>
+            </div>
           </div>
         </el-col>
       </el-row>
@@ -239,7 +251,20 @@
             <el-radio-button label="本周"></el-radio-button>
             <el-radio-button label="本月"></el-radio-button>
             <el-radio-button label="上月"></el-radio-button>
+            <el-radio-button label="指定日期" v-if="!showCompanyField"></el-radio-button>
           </el-radio-group>
+          <el-date-picker
+      v-if="queryTime === '指定日期'"
+      v-model="specificDate"
+      type="date"
+      placeholder="请选择日期"
+      :picker-options="pickerOptions"
+      format="yyyy-MM-dd"
+      value-format="yyyy-MM-dd"
+      size="medium"
+      style=" width: 150px;"
+      @change="handleAnalysis"
+    ></el-date-picker>
         </div>
         <div class="action-group">
           <div v-if="this.$store.state.user.medicalMallConfig.statics">
@@ -575,6 +600,7 @@ import {
   smsBalance, thisMonthOrderCount, thisMonthRecvCount, trafficLog,
   watchCourseTopTen, watchEndPlayTrend,getWatchCourseStatisticsData
 } from "@/api/statistics/statistics";
+import { getFailedCount } from "@/api/live/wxExpressTask";
 import dayjs from 'dayjs';
 import { listDept } from '@/api/system/dept'
 import { listCompany } from '@/api/his/company'
@@ -593,6 +619,7 @@ const viewCharOption = {
     bottom: '3%',
     containLabel: true
   },
+  projectFrom:process.env.VUE_APP_PROJECT_FROM,
   xAxis: {
     type: 'category',
     data: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23']
@@ -1051,9 +1078,23 @@ export default {
       // 商品总数
       goodsTotalNum: 0,
       // 今日商品总数
-      todayGoodsNum: 0
+      todayGoodsNum: 0,
+      // 微信上传失败订单数量
+      wxFailedCount: 0,
+      specificDate:null,
+      pickerOptions: {
+        disabledDate(time) {
+          // 可选:限制只能选择今天及以前的日期
+          return time.getTime() > Date.now();
+        },},
     }
   },
+  computed: {
+    // 计算属性判断是否显示
+    showCompanyField() {
+      return process.env.VUE_APP_TITLE_INDEX === '恒春来';
+    },
+  },
   mounted() {
     this.$nextTick(() => {
       this.initViewerChart();
@@ -1339,6 +1380,14 @@ export default {
           }
         }
       })
+      // 获取微信上传失败订单数量
+      getFailedCount().then(res => {
+        if (res.code === 200) {
+          this.wxFailedCount = res.data || 0;
+        }
+      }).catch(() => {
+        this.wxFailedCount = 0;
+      })
       authorizationInfo(this.staticParam).then(res => {
         if (res.code === 200 && res.data != null) {
           this.todayWatchUserCount = res.data.todayWatchUserCount;
@@ -1459,7 +1508,11 @@ export default {
         param.startTime = this.formatDate(lastMonth.startOf('month'));
         param.endTime = this.formatDate(lastMonth.endOf('month'));
         type = 4;
-      } else {
+      } else if(this.queryTime === '指定日期'){
+        param.startTime = this.formatDate(this.specificDate);
+        param.endTime = this.formatDate(this.specificDate);
+        type = 5;
+      }else {
         console.warn(`未知的 queryTime: ${this.queryTime}, 默认使用今日`);
         param.startTime = this.formatDate(today);
         param.endTime = this.formatDate(today);
@@ -1470,7 +1523,10 @@ export default {
     },
     // 分析概览
     handleAnalysis(e) {
-
+      if(this.queryTime === '指定日期' && this.specificDate === null ){
+        return;
+      }
+      if(this.queryTime != '指定日期')this.specificDate = null;
       let param = this.getParam();
       analysisPreview(param).then(res => {
         if (res.code === 200) {

+ 138 - 34
src/views/course/coursePlaySourceConfig/index.vue

@@ -207,6 +207,13 @@
             v-hasPermi="['course:playSourceConfig:unbind']"
             v-if="scope.row.merchantConfigId"
           >解绑</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-document"
+            @click="handleAgreement(scope.row)"
+            v-hasPermi="['course:playSourceConfig:agreement']"
+          >协议配置</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -347,9 +354,9 @@
           </div>
         </el-form-item>
 
-        <el-form-item 
+        <el-form-item
           v-if="form.customAuthEnabled === 1"
-          label="授权方式" 
+          label="授权方式"
           prop="miniAppAuthType"
         >
           <el-radio-group v-model="form.miniAppAuthType">
@@ -364,9 +371,9 @@
           </div>
         </el-form-item>
 
-        <el-form-item 
+        <el-form-item
           v-if="form.customAuthEnabled === 1 && form.miniAppAuthType === 2"
-          label="授权域名" 
+          label="授权域名"
           prop="userCourseAuthDomain"
         >
           <el-input
@@ -386,6 +393,41 @@
       </div>
     </el-dialog>
 
+    <!-- 协议配置对话框 -->
+    <el-dialog title="协议配置" :visible.sync="agreementDialogVisible" width="1000px" append-to-body>
+      <el-form ref="agreementForm" :model="agreementForm" :rules="agreementRules" label-width="150px" style="margin-right: 25px">
+        <el-form-item label="医生注册协议" prop="doctorRegister">
+          <editor v-model="agreementForm.doctorRegister" :min-height="200"/>
+        </el-form-item>
+        <el-form-item label="医生多机构备案协议" prop="doctorFiling">
+          <editor v-model="agreementForm.doctorFiling" :min-height="200"/>
+        </el-form-item>
+        <el-form-item label="用户协议" prop="userRegister">
+          <editor v-model="agreementForm.userRegister" :min-height="200"/>
+        </el-form-item>
+        <el-form-item label="隐私协议" prop="userPrivacy">
+          <editor v-model="agreementForm.userPrivacy" :min-height="200"/>
+        </el-form-item>
+        <el-form-item label="健康客服协议" prop="userHealth">
+          <editor v-model="agreementForm.userHealth" :min-height="200"/>
+        </el-form-item>
+        <el-form-item label="会员服务协议" prop="vipService">
+          <editor v-model="agreementForm.vipService" :min-height="200"/>
+        </el-form-item>
+        <el-form-item label="会员自动续费协议" prop="vipAutomaticService">
+          <editor v-model="agreementForm.vipAutomaticService" :min-height="200"/>
+        </el-form-item>
+        <el-form-item label="用户注销协议" prop="userRemoveService">
+          <editor v-model="agreementForm.userRemoveService" :min-height="200"/>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="agreementDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitAgreementForm">确 定</el-button>
+      </div>
+    </el-dialog>
+
+
     <!-- 绑定  -->
     <el-dialog :title="bindForm.bindTitle" :visible.sync="bindForm.bindShow" width="800px" append-to-body :before-close="handleBindClose">
       <el-form ref="bindForm" :model="bindForm" :rules="bindRules" label-width="130px">
@@ -427,7 +469,9 @@ import {
   add,
   del,
   updateBindConfig,
-  updateUnbindConfig
+  updateUnbindConfig,
+  updateAgreementConfig,
+  queryAgreementConfig
 } from '@/api/course/coursePlaySourceConfig'
 import {updateIsTownOn} from "@/api/system/config";
 import { allList } from '@/api/company/company'
@@ -463,24 +507,27 @@ export default {
         appid: null,
         status: null
       },
-      statusOptions: [
-        {
-          label: "待审核",
-          value: -1
-        },
-        {
-          label: "正常",
-          value: 0
-        },
-        {
-          label: "半封禁",
-          value: 1
-        },
-        {
-          label: "封禁",
-          value: 2
-        }
-      ],
+      // 协议配置相关
+      agreementDialogVisible: false,
+      agreementForm: {
+        appId: null,
+        doctorRegister: '',
+        doctorFiling: '',
+        userRegister: '',
+        userPrivacy: '',
+        userHealth: '',
+        vipService: '',
+        vipAutomaticService: '',
+        userRemoveService: ''
+      },
+      agreementRules: {
+        // doctorRegister: [
+        //   { required: true, message: "医生注册协议不能为空", trigger: "blur" }
+        // ],
+        // userRegister: [
+        //   { required: true, message: "用户协议不能为空", trigger: "blur" }
+        // ]
+      },
       showSearch: true,
       single: true,
       multiple: true,
@@ -558,19 +605,19 @@ export default {
           { required: true, message: "msgDataFormat不能为空", trigger: "blur" }
         ],
         miniAppAuthType: [
-          { 
+          {
             validator: (rule, value, callback) => {
               if (this.form.customAuthEnabled === 1 && !value) {
                 callback(new Error('开启自定义授权时必须选择授权方式'));
               } else {
                 callback();
               }
-            }, 
-            trigger: 'change' 
+            },
+            trigger: 'change'
           }
         ],
         userCourseAuthDomain: [
-          { 
+          {
             validator: (rule, value, callback) => {
               if (this.form.customAuthEnabled === 1 && this.form.miniAppAuthType === 2) {
                 if (!value) {
@@ -583,8 +630,8 @@ export default {
               } else {
                 callback();
               }
-            }, 
-            trigger: 'blur' 
+            },
+            trigger: 'blur'
           }
         ]
       }
@@ -631,6 +678,63 @@ export default {
       this.companyOptions = [];
       this.open = false;
     },
+    // 协议配置处理
+    handleAgreement(row) {
+      // 获取当前行的协议相关信息
+      queryAgreementConfig({appid:row.appid}).then(response => {
+        if (response.code === 200) {
+          // 返回agreementData json串
+          let agreementData = JSON.parse(response.data.agreementData);
+          this.agreementForm = {
+            appId: row.appid,
+            doctorRegister: agreementData.doctorRegister || '',
+            doctorFiling: agreementData.doctorFiling || '',
+            userRegister: agreementData.userRegister || '',
+            userPrivacy: agreementData.userPrivacy || '',
+            userHealth: agreementData.userHealth || '',
+            vipService: agreementData.vipService || '',
+            vipAutomaticService: agreementData.vipAutomaticService || '',
+            userRemoveService: agreementData.userRemoveService || ''
+          };
+
+          this.agreementDialogVisible = true;
+        }
+      })
+
+
+    },
+    // 提交协议配置
+    submitAgreementForm() {
+      this.$refs["agreementForm"].validate(valid => {
+        if (valid) {
+          // 调用API更新协议配置
+          const params = {
+            appId: this.agreementForm.appId,
+            agreementData: JSON.stringify({
+              doctorRegister: this.agreementForm.doctorRegister,
+              doctorFiling: this.agreementForm.doctorFiling,
+              userRegister: this.agreementForm.userRegister,
+              userPrivacy: this.agreementForm.userPrivacy,
+              userHealth: this.agreementForm.userHealth,
+              vipService: this.agreementForm.vipService,
+              vipAutomaticService: this.agreementForm.vipAutomaticService,
+              userRemoveService: this.agreementForm.userRemoveService
+            })
+          };
+          updateAgreementConfig(params).then(response => {
+            if (response.code === 200) {
+              this.msgSuccess("协议配置更新成功");
+              this.agreementDialogVisible = false;
+              this.getList(); // 刷新列表
+            } else {
+              this.msgError("协议配置更新失败:" + response.msg);
+            }
+          }).catch(error => {
+            this.msgError("请求失败:" + error.message);
+          });
+        }
+      });
+    },
     handleUnbind(row) {
       this.$confirm('是否确认解绑该配置?', "警告", {
         confirmButtonText: "确定",
@@ -701,13 +805,13 @@ export default {
       this.bindCurrentRow = row;  // 保存当前行数据
       this.bindForm.bindShow = true;
     },
-    changeSysPayModes(value){
-      console.log(value)
+    changeSysPayModes(value,row){
       const query = {
         pageNum: 1,
         pageSize: 100,
         merchantType: value,
-        isDeleted: 0
+        isDeleted: 0,
+        appId:this.bindCurrentRow.appid
       }
       listMerchantAppConfig(query).then( response => {
           this.merchantAppConfigList = response.rows;
@@ -849,12 +953,12 @@ export default {
           }else{
             this.form.setCompanyIds = "";
           }
-          
+
           if (this.form.customAuthEnabled === 0) {
             this.form.miniAppAuthType = null;
             this.form.userCourseAuthDomain = null;
           }
-          
+
           console.log(this.form);
           if (this.form.id != null) {
             update(this.form).then(response => {

+ 403 - 0
src/views/course/courseRedPacketLogCount/index.vue

@@ -0,0 +1,403 @@
+<template>
+  <div class="course-red-packet-statistics">
+    <!-- 查询条件 -->
+    <el-card class="search-card">
+      <el-form :model="queryParams" ref="queryParams" label-width="80px" inline>
+          <el-form-item label="筛选类型" prop="status">
+            <el-select v-model="queryParams.changeType" placeholder="请选择筛选类型" clearable style="width: 100%" @change="changeType">
+              <el-option label="按销售公司" :value="1"></el-option>
+              <el-option label="按员工" :value="2"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="公司名" prop="companyId" v-if="queryParams.changeType ==1 ">
+            <el-select filterable style="width: 220px" v-model="queryParams.companyIds"
+                       @change="handleSeller" placeholder="请选择公司名"
+                       clearable size="small"
+                       multiple >
+              <el-option
+                v-for="item in companys"
+                :key="item.companyId"
+                :label="item.companyName"
+                :value="item.companyId"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="选择员工" prop="userIds" v-if="queryParams.changeType ==2 ">
+            <select-tree
+              v-model="selectedCompanyList"
+              :raw-data="deptList"
+              :parentSelectable="true"
+              placeholder="请选择销售"
+              :multiple="true"
+              component-width="300px"
+              :max-display-tags="3"
+              :check-strictly="false"
+              :return-leaf-only="false"
+            ></select-tree>
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 100%">
+              <el-option label="全部" value=""></el-option>
+              <el-option label="发送中" value="0"></el-option>
+              <el-option label="已发送" value="1"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="createTimeText"
+              type="datetimerange"
+              range-separator="至"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              value-format="yyyy-MM-dd HH:mm:ss"
+              @change="createChange"
+              :default-time="['00:00:00', '23:59:59']"
+            />
+          </el-form-item>
+        <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:courseRedPacketLog:countExport']"
+            >导出
+            </el-button>
+          </el-col>
+          <el-col :span="24" class="text-right">
+            <el-button type="primary" @click="handleSearch">查询</el-button>
+            <el-button @click="resetQuery">重置</el-button>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-card>
+
+    <!-- 统计结果 -->
+    <el-card class="result-card">
+      <div slot="header">
+        <span>红包发送统计</span>
+      </div>
+
+      <el-table :data="statisticsData" border style="width: 100%" v-loading="loading"
+                show-summary :summary-method="getSummaries"> >
+        <el-table-column prop="companyName" label="公司名称" align="center"></el-table-column>
+        <div v-if="queryParams.changeType==2">
+          <el-table-column prop="nickName" label="员工姓名" align="center"></el-table-column>
+          <el-table-column prop="companyUserId" label="员工编号" align="center"></el-table-column>
+          <el-table-column prop="deptId" label="部门编号" align="center"></el-table-column>
+          <el-table-column prop="deptName" label="部门昵称" align="center"></el-table-column>
+        </div>
+        <el-table-column prop="amount" label="已发红包金额" align="center"></el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total>0"
+        :total="total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </el-card>
+  </div>
+</template>
+
+<script>
+import {getCompanyList} from "@/api/company/company";
+import {getUserList} from "@/api/company/companyUser";
+import SelectTree from '@/components/TreeSelect/index.vue'
+import { getDeptData } from '@/api/system/employeeStats'
+import {
+  exportCourseRedPacketLogCountExport,
+  getRedPacketLogCount
+} from '@/api/course/courseRedPacketLog'
+
+export default {
+  name: 'courseRedPacketLogCount',
+  components: { SelectTree },
+  data() {
+    return {
+      // 总条数
+      total: 0,
+      companys:[],
+      companyUserList:[],
+      selectedCompanyList: [],
+      deptList: [],
+      createTimeText: [],
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        changeType: 1,
+        companyId: null,
+        companyIds: null,
+        companyUserId: null,
+        startTime:null,
+        endTime:null,
+        status: null,
+        sTime:null,
+        eTime:null,
+        userIds: null
+      },
+      exportLoading: false,
+      statisticsData: [],
+      loading: false,
+      pageInfo: {
+        currentPage: 1,
+        pageSize: 10,
+        total: 0
+      }
+    }
+  },
+  mounted() {
+  },
+  created() {
+      getCompanyList().then(response => {
+          this.companys = response.data;
+          if(this.companys!=null&&this.companys.length>0){
+            this.companyId=this.companys[0].companyId;
+          }
+      });
+
+    getDeptData().then(response => {
+      this.deptList = response.data;
+    })
+  },
+  methods: {
+
+    changeType(){
+      this.statisticsData=[];
+      if (this.queryParams.changeType == 1){
+        this.selectedCompanyList=[];
+      }
+      if (this.queryParams.changeType == 2){
+        this.queryParams.companyIds=null;
+      }
+    },
+
+    getSummaries(param) {
+      const { columns, data } = param; // data 就是当前表格的 statisticsData
+      const sums = [];
+
+      columns.forEach((column, index) => {
+        if (index === 0) {
+          sums[index] = '总计';
+          return;
+        }
+
+        if (column.property === 'amount') {
+          // 直接从当前表格数据计算
+          const total = data.reduce((sum, item) => {
+            return sum + (parseFloat(item.amount) || 0);
+          }, 0);
+          sums[index] = '¥' + this.formatAmount(total);
+        } else {
+          sums[index] = '';
+        }
+      });
+
+      return sums;
+    },
+
+
+    // 格式化金额(不四舍五入,保留两位小数)
+    formatAmount(value) {
+      if (isNaN(value)) return '0.00';
+
+      // 方法1:使用 Math.floor 截断
+      const truncated = Math.floor(value * 100) / 100;
+
+      // 方法2:更精确的截断方法
+      // const truncated = Number(value.toString().match(/^\d+(?:\.\d{0,2})?/));
+
+      return truncated.toFixed(2);
+    },
+
+
+    handleSearch() {
+      this.pageInfo.currentPage = 1
+      this.getList();
+    },
+
+    createChange(createTime) {
+      if (createTime && createTime.length >= 2) {
+        if(!this.checkDateRangeLimit(createTime)){
+          this.createTimeText = null;
+          this.queryParams.sTime=null;
+          this.queryParams.eTime=null;
+          return;
+        }
+
+        this.queryParams.sTime = this.formatDate(createTime[0]) || null;
+        this.queryParams.eTime = this.formatDate(createTime[1]) || null;
+      } else {
+        this.createTimeText = [];
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    formatDate(date) {
+      if (!date) return ''
+
+      // 确保 date 是 Date 对象
+      let dateObj = date
+      if (typeof date === 'string') {
+        dateObj = new Date(date)
+      }
+
+      // 如果转换失败,返回空字符串
+      if (!(dateObj instanceof Date) || isNaN(dateObj.getTime())) {
+        return ''
+      }
+
+      // 使用更安全的格式化方法
+      const year = dateObj.getFullYear()
+      const month = String(dateObj.getMonth() + 1).padStart(2, '0')
+      const day = String(dateObj.getDate()).padStart(2, '0')
+      const hours = String(dateObj.getHours()).padStart(2, '0')
+      const minutes = String(dateObj.getMinutes()).padStart(2, '0')
+      const seconds = String(dateObj.getSeconds()).padStart(2, '0')
+
+      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+    },
+    checkDateRangeLimit(dateRange) {
+      if (dateRange && dateRange.length >= 2) {
+        const startDate = new Date(dateRange[0]);
+        const endDate = new Date(dateRange[1]);
+
+        // 设置时间为当天开始,避免时间部分影响计算
+        startDate.setHours(0, 0, 0, 0);
+        endDate.setHours(0, 0, 0, 0);
+
+        const timeDiff = Math.abs(endDate - startDate);
+        const diffDays = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
+
+        if (diffDays > 31) { // maxDays-1 因为包含起始日
+          this.$message.warning('时间区间不能超过一个月');
+          return false;
+        }
+      }
+      return true;
+    },
+    handleSeller(){
+
+      if(this.queryParams.companyId != null) {
+        getUserList(this.queryParams.companyId).then(res=>{
+          if(res.code === 200) {
+            this.companyUserList = res.data
+          }
+        })
+      }
+    },
+
+    resetQuery() {
+      this.$refs.queryParams.resetFields();
+      this.createTimeText = [];
+      this.queryParams.dateRange = [];
+      this.selectedCompanyList = [];
+      this.statisticsData=[];
+      this.handleSearch()
+    },
+
+    getList() {
+
+      if (this.queryParams.changeType == 1 && this.queryParams.companyIds.length == 0) {
+        return this.$message.warning('请选择公司');
+      }
+
+      if (this.isEmptyArray(this.createTimeText)) {
+        this.$message.warning('请选择创建时间');
+        return;
+      }
+
+      console.log(this.queryParams.changeType)
+      console.log(this.selectedCompanyList)
+
+      if (this.queryParams.changeType == 2){
+        if( this.selectedCompanyList != null && this.selectedCompanyList.length > 0) {
+          this.queryParams.userIds = this.selectedCompanyList;
+        }else {
+          return this.$message.warning('请选择员工');
+        }
+      }
+
+      this.loading = true
+      // 查询统计
+      getRedPacketLogCount(this.queryParams).then(response => {
+        this.statisticsData = response.data.list;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    // 添加辅助方法
+    isEmptyArray(arr) {
+      return !arr || arr.length === 0;
+    },
+    handleSizeChange(val) {
+      this.pageInfo.pageSize = val
+      this.pageInfo.currentPage = 1
+      this.getList()
+    },
+
+    handleCurrentChange(val) {
+      this.pageInfo.currentPage = val
+      this.getList()
+    },
+
+    /** 导出按钮操作 */
+    handleExport() {
+
+      if(this.selectedCompanyList != null && this.selectedCompanyList.length > 0) {
+        this.queryParams.userIds = this.selectedCompanyList;
+      }else {
+        this.queryParams.userIds = [];
+      }
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有红包记录数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        const loadingInstance = this.$loading({
+          lock: true,
+          text: '正在导出数据,请稍候...',
+          background: 'rgba(0, 0, 0, 0.7)'
+        });
+
+        this.exportLoading = true;
+
+        return exportCourseRedPacketLogCountExport(queryParams).finally(res=>{
+          loadingInstance.close();
+        })
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {}).finally(res=>{
+
+      });
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.course-red-packet-statistics {
+  padding: 20px;
+
+  .search-card {
+    margin-bottom: 20px;
+  }
+
+  .result-card {
+    .pagination-container {
+      margin-top: 20px;
+      text-align: right;
+    }
+  }
+
+  .text-right {
+    text-align: right;
+  }
+}
+</style>

+ 67 - 3
src/views/course/courseUserStatistics/qw/statistics.vue

@@ -13,7 +13,9 @@
       <el-form-item label="添加时间" prop="createTime">
         <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="change"></el-date-picker>
       </el-form-item>
-      <el-form-item label="公司名称" prop="companyId">
+      
+      <el-form-item label="公司名称" prop="companyId" v-if="!showCompanyField"
+      >
         <el-select style="width: 220px" filterable v-model="queryParams.companyId" placeholder="请选择公司名" clearable size="small">
           <el-option
             v-for="item in companys"
@@ -22,7 +24,32 @@
             :value="item.companyId"
           />
         </el-select>
+      </el-form-item> 
+      <el-form-item label="公司" prop="companyId" v-else>
+        <select-tree
+            v-model="selectedCompanyList"
+            :raw-data="deptList"
+            :parentSelectable="true"
+            placeholder="请选择销售"
+            :multiple="true"
+            component-width="300px"
+            :max-display-tags="3"
+            :check-strictly="false"
+            :return-leaf-only="false"
+            @change="handleMultiChange"
+        ></select-tree>
       </el-form-item>
+      <!-- <el-form-item label="公司名称" prop="companyId" v-else
+      >
+        <el-select style="width: 220px" filterable v-model="queryParams.companyId" placeholder="请选择公司名" clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
+      </el-form-item> -->
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -462,11 +489,14 @@
 </template>
 
 <script>
+import SelectTree from "@/components/TreeSelect/index.vue";
+import { getDeptData } from '@/api/system/employeeStats'
 import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,statisticsList,qwWatchLogStatisticsList,qwWatchLogAllStatisticsList } from "@/api/course/courseWatchLog";
 import { courseList,videoList } from '@/api/course/courseRedPacketLog'
 import {getCompanyList} from "@/api/company/company";
 export default {
   name: "CourseWatchLog",
+  components: {SelectTree},
   data() {
     return {
       companys: [],
@@ -513,6 +543,7 @@ export default {
         eTime:null,
         scheduleStartTime: null,
         scheduleEndTime: null,
+        userIds:[],
       },
       // 表单参数
       form: {},
@@ -520,8 +551,18 @@ export default {
       rules: {
       },
       scheduleTime: null,
+       // 选中的公司列表
+    selectedCompanyList: null,
+    // 部门/公司树形数据
+    deptList: [],
     };
   },
+computed: {
+    // 计算属性判断是否显示
+    showCompanyField() {
+      return process.env.VUE_APP_TITLE_INDEX === '挑宝网';
+    },
+  },
   created() {
     getCompanyList().then(response => {
       this.companys = response.data;
@@ -534,6 +575,7 @@ export default {
     this.getDicts("sys_course_watch_log_type").then(response => {
       this.logTypeOptions = response.data;
     });
+    this.initCompanyData();
   },
   methods: {
     courseChange(row){
@@ -566,13 +608,18 @@ export default {
     },
     /** 查询短链课程看课记录列表 */
     getList() {
+    if(this.selectedCompanyList != null && this.selectedCompanyList.length > 0) {
+        this.queryParams.userIds = JSON.stringify(this.selectedCompanyList);
+    } else {
+        this.queryParams.userIds = [];
+    }
       this.loading = true;
-      qwWatchLogAllStatisticsList(this.queryParams).then(response => {
+    qwWatchLogAllStatisticsList(this.queryParams).then(response => {
         this.courseWatchLogList = response.rows;
         this.total = response.total;
       }).finally(()=>{
         this.loading = false;
-      })
+      })      
     },
     // 取消按钮
     cancel() {
@@ -604,6 +651,7 @@ export default {
     },
     /** 重置按钮操作 */
     resetQuery() {
+        this.selectedCompanyList = []; // 清空公司选择
       this.resetForm("queryForm");
       this.createTime = null;
       this.scheduleTime = null;
@@ -671,6 +719,11 @@ export default {
     },
     /** 导出按钮操作 */
     handleExport() {
+        if(this.selectedCompanyList != null && this.selectedCompanyList.length > 0) {
+    this.queryParams.userIds = this.selectedCompanyList;
+  } else {
+    this.queryParams.userIds = [];
+  }
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有短链课程看课记录数据项?', "警告", {
           confirmButtonText: "确定",
@@ -693,6 +746,17 @@ export default {
         this.queryParams.scheduleEndTime = null;
       }
     },
+    // 公司选择框变化处理
+  handleMultiChange(e) {
+    // 处理公司选择变化
+    // console.log(this.selectedCompanyList)
+  },
+  // 初始化获取公司数据
+  initCompanyData() {
+    getDeptData().then(response => {
+      this.deptList = response.data;
+    });
+  },
   }
 };
 </script>

+ 77 - 2
src/views/course/courseWatchLog/qw/statistics.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="app-container">
     <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="公司名" prop="companyId">
+      <el-form-item label="公司名" prop="companyId" v-if="!showCompanyField">
         <el-select filterable style="width: 220px" v-model="queryParams.companyId" placeholder="请选择公司名" clearable size="small">
           <el-option
             v-for="item in companys"
@@ -11,6 +11,20 @@
           />
         </el-select>
       </el-form-item>
+      <el-form-item label="公司" prop="companyId" v-else>
+        <select-tree
+            v-model="selectedCompanyList"
+            :raw-data="deptList"
+            :parentSelectable="true"
+            placeholder="请选择销售"
+            :multiple="true"
+            component-width="300px"
+            :max-display-tags="3"
+            :check-strictly="false"
+            :return-leaf-only="false"
+            @change="handleMultiChange"
+        ></select-tree>
+      </el-form-item>
       <el-form-item label="课程" prop="courseId">
         <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
           <el-option
@@ -21,6 +35,17 @@
           />
         </el-select>
       </el-form-item>
+
+      <el-form-item label="档期" prop="qwSopId">
+        <el-select filterable v-model="queryParams.qwSopId" placeholder="请选择档期" clearable size="small">
+          <el-option
+            v-for="item in scheduleList"
+            :key="item.qwSopId"
+            :label="item.qwSopName"
+            :value="item.qwSopId"
+          />
+        </el-select>
+      </el-form-item>
       <el-form-item label="小节" prop="videoId">
         <el-select filterable  v-model="queryParams.videoId" placeholder="请选择小节"  clearable size="small">
           <el-option
@@ -111,6 +136,8 @@
 </template>
 
 <script>
+  import SelectTree from "@/components/TreeSelect/index.vue";
+import { getDeptData } from '@/api/system/employeeStats'
 import {
   listCourseWatchLog,
   getCourseWatchLog,
@@ -125,10 +152,14 @@ import {
 } from '@/api/course/qw/courseWatchLog'
 import { courseList,videoList } from '@/api/course/courseRedPacketLog'
 import {getCompanyList} from "@/api/company/company";
+  import { fetchScheduleList } from "@/api/course/qw/courseWatchLog";
 export default {
   name: "CourseWatchLog",
+    components: {SelectTree},
+
   data() {
     return {
+      scheduleList: [], // 档期列表,结构为[{qwSopId: 'xxx', qwSopName: 'xxx'}]
       signProjectName:"",
       companys:[],
       activeName:"00",
@@ -170,10 +201,12 @@ export default {
         companyUserId: null,
         companyId: null,
         courseId: null,
+        qwSopId: null, // 档期ID
         sTime:null,
         eTime:null,
         scheduleStartTime: null,
         scheduleEndTime: null,
+        userIds:[],
       },
       // 表单参数
       form: {},
@@ -181,8 +214,18 @@ export default {
       rules: {
       },
       scheduleTime: null,
+             // 选中的公司列表
+    selectedCompanyList: null,
+    // 部门/公司树形数据
+    deptList: [],
     };
   },
+  computed: {
+    // 计算属性判断是否显示
+    showCompanyField() {
+      return process.env.VUE_APP_TITLE_INDEX === '挑宝网';
+    },
+  },
   created() {
     getSignProjectName().then(res=>{
       this.signProjectName = res.signProjectName;
@@ -203,10 +246,24 @@ export default {
     this.getDicts("sys_course_watch_log_type").then(response => {
       this.logTypeOptions = response.data;
     });
+    this.initCompanyData();
+    // 获取档期列表
+    this.getScheduleList();
   },
   methods: {
+    // 获取档期列表
+    getScheduleList() {
+      fetchScheduleList({}).then(response => {
+        this.scheduleList = response.rows; // rows包含[{qwSopId: 'xxx', qwSopName: 'xxx'}]格式的数据
+      });
+    },
 
     handleStatisExport(){
+      if(this.selectedCompanyList != null && this.selectedCompanyList.length > 0) {
+    this.queryParams.userIds = this.selectedCompanyList;
+  } else {
+    this.queryParams.userIds = [];
+  }
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有会员看课统计数据项?', "警告", {
         confirmButtonText: "确定",
@@ -281,6 +338,11 @@ export default {
     },
     /** 查询短链课程看课记录列表 */
     getList() {
+      if(this.selectedCompanyList != null && this.selectedCompanyList.length > 0) {
+        this.queryParams.userIds = JSON.stringify(this.selectedCompanyList);
+    } else {
+        this.queryParams.userIds = [];
+    }
       this.loading = true;
       statisticsList(this.queryParams).then(response => {
         this.courseWatchLogList = response.rows;
@@ -313,7 +375,7 @@ export default {
     },
     /** 搜索按钮操作 */
     handleQuery() {
-      if(this.queryParams.companyId == null) {
+      if(this.queryParams.companyId == null && (this.selectedCompanyList == null ||this.selectedCompanyList.length == 0)) {
         this.$message.warning("公司不能为空!")
         return;
       }
@@ -322,6 +384,7 @@ export default {
     },
     /** 重置按钮操作 */
     resetQuery() {
+      this.selectedCompanyList = [];
       this.resetForm("queryForm");
       this.createTime = null;
       this.scheduleTime = null;
@@ -329,6 +392,7 @@ export default {
       this.queryParams.eTime = null;
       this.queryParams.scheduleStartTime = null;
       this.queryParams.scheduleEndTime = null;
+      this.queryParams.qwSopId = null; // 重置档期选择
       this.handleQuery();
     },
     // 多选框选中数据
@@ -411,6 +475,17 @@ export default {
         this.queryParams.scheduleEndTime = null;
       }
     },
+        // 公司选择框变化处理
+  handleMultiChange(e) {
+    // 处理公司选择变化
+    // console.log(this.selectedCompanyList)
+  },
+  // 初始化获取公司数据
+  initCompanyData() {
+    getDeptData().then(response => {
+      this.deptList = response.data;
+    });
+  },
   }
 };
 </script>

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

@@ -22,6 +22,15 @@
           />
         </el-select>
       </el-form-item>
+      <el-form-item label="课堂id" prop="courseId">
+        <el-input
+          v-model="queryParams.courseId"
+          placeholder="请输入课堂id"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
       <el-form-item label="课堂名称" prop="courseName">
         <el-input
           v-model="queryParams.courseName"
@@ -433,6 +442,9 @@
           <i class="el-icon-warning"/>
           <span style="color: rgb(153, 169, 191)"> 多个支持单位,请用英文逗号隔开</span>
         </el-form-item>
+        <el-form-item label="监督投诉电话" prop="complaintPhone">
+          <el-input v-model="configDialog.form.complaintPhone" placeholder="请输入监督投诉电话" clearable/>
+        </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary"
@@ -574,6 +586,7 @@ export default {
         hotRanking: null,
         integral: null,
         price: null,
+        courseId: null,
         isPrivate: 1,
         companyIdsList:[],
         sorting: "1",
@@ -649,7 +662,9 @@ export default {
           teamEnable: 0,
           team: null,
           supportEnable: 0,
-          support: null
+          support: null,
+          //监督投诉电话
+          complaintPhone: null,
         },
         rules: {
           tv: [

+ 168 - 2
src/views/course/userCourseCategory/index.vue

@@ -70,6 +70,28 @@
           v-hasPermi="['course:userCourseCategory:export']"
         >导出</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="info"
+          icon="el-icon-upload2"
+          size="mini"
+          @click="handleImport"
+          v-hasPermi="['course:userCourseCategory:importData']"
+        >导入</el-button>
+      </el-col>
+
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleFansExport"
+          v-hasPermi="['course:userCourseCategory:fansExport']"
+        >重粉导出</el-button>
+      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
@@ -79,7 +101,9 @@
       :data="userCourseCategoryList"
       row-key="cateId"
       :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
+    @selection-change="handleSelectionChange"
     >
+      <el-table-column type="selection" width="55" align="center"/>
       <el-table-column label="分类id" align="left" prop="cateId" />
       <el-table-column label="分类名称" align="left" prop="cateName" />
       <el-table-column  label="状态" align="center" prop="isShow">
@@ -145,13 +169,47 @@
         <el-button @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
+
+    <!-- 导入 -->
+    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
+      <el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">
+          将文件拖到此处,或
+          <em>点击上传</em>
+        </div>
+        <div class="el-upload__tip" slot="tip">
+          <el-link type="info" style="font-size:12px" @click="importTemplate">下载模板</el-link>
+        </div>
+        <div class="el-upload__tip" style="color:red" slot="tip">提示:仅允许导入“xls”或“xlsx”格式文件!</div>
+      </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" :loading="upload.isUploading" :disabled="upload.isUploading" @click="submitFileForm">确 定</el-button>
+        <el-button @click="upload.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog title="导入结果" :close-on-press-escape="false" :close-on-click-modal="false" :visible.sync="importMsgOpen" width="500px" append-to-body>
+      <div class="import-msg" v-html="importMsg">
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="importMsgOpen = false">关 闭</el-button>
+        <el-button
+          v-if="failList && failList.length > 0"
+          type="primary"
+          v-hasPermi="['course:userCourseCategory:exportFail']"
+          @click="handleExportFailList(failList)"
+          :loading="exportFailLoading"
+        >导出失败信息</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import { listUserCourseCategory, getUserCourseCategory, delUserCourseCategory, addUserCourseCategory, updateUserCourseCategory, exportUserCourseCategory } from "@/api/course/userCourseCategory";
+import { listUserCourseCategory, getUserCourseCategory, delUserCourseCategory, addUserCourseCategory, updateUserCourseCategory, exportUserCourseCategory, exportFans,importTemplate, exportFail } from "@/api/course/userCourseCategory";
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import { getToken } from '@/utils/auth'
 export default {
   name: "UserCourseCategory",
   components: {
@@ -201,6 +259,23 @@ export default {
         cateName: [
           { required: true, message: "分类名称不能为空", trigger: "blur" }
         ],
+      },
+      // 导入
+      importMsgOpen:false,
+      exportFailLoading: false,
+      failList:[],
+      importMsg: '',
+      upload: {
+        // 是否显示弹出层(文件导入)
+        open: false,
+        // 弹出层标题(文件导入)
+        title: "",
+        // 是否禁用上传
+        isUploading: false,
+        // 设置上传的请求头部
+        headers: { Authorization: "Bearer " + getToken() },
+        // 上传的地址
+        url: process.env.VUE_APP_BASE_API + "/course/userCourseCategory/importData",
       }
     };
   },
@@ -273,6 +348,8 @@ export default {
       this.ids = selection.map(item => item.cateId)
       this.single = selection.length!==1
       this.multiple = !selection.length
+
+      console.log("selection",  selection);
     },
     /** 新增按钮操作 */
     handleAdd() {
@@ -341,7 +418,96 @@ export default {
           this.download(response.msg);
           this.exportLoading = false;
         }).catch(() => {});
+    },
+    // 下载模板
+    importTemplate() {
+      importTemplate().then((response) => {
+        this.download(response.msg);
+      });
+    },
+    // 导入
+    handleImport() {
+      this.upload.title = "导入题目";
+      this.upload.open = true;
+    },
+    submitFileForm() {
+      this.$refs.upload.submit();
+    },
+    // 文件上传中处理
+    handleFileUploadProgress(event, file, fileList) {
+      this.upload.isUploading = true;
+    },
+    // 文件上传成功处理
+    handleFileSuccess(response, file, fileList) {
+      this.upload.open = false;
+      this.upload.isUploading = false;
+      this.$refs.upload.clearFiles();
+      this.importMsgOpen=true;
+      this.importMsg = response.data.message
+      this.failList = response.data.failList
+      this.getList();
+    },
+    // 导出失败信息
+    handleExportFailList(failList) {
+      if (!failList || failList.length === 0) {
+        this.msgError("没有失败信息可导出");
+        return;
+      }
+      this.exportFailLoading = true;
+      this.$confirm('是否确认导出失败信息?', "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "info"
+      }).then(() => {
+        // 调用导出失败信息的API
+        exportFail(failList).then(response => {
+          this.download(response.msg);
+          this.msgSuccess("失败信息导出成功");
+          this.exportFailLoading = false;
+        }).catch(() => {
+          this.msgError("失败信息导出失败");
+          this.exportFailLoading = false;
+        });
+      }).catch(() => {
+        this.exportFailLoading = false;
+      });
+    },
+    handleFansExport() {
+      if (!Array.isArray(this.ids) || this.ids.length === 0) {
+        this.$message.warning('请先选择至少一条数据');
+        return;
+      }
+
+      const params = { ids: this.ids };
+
+      this.$confirm('是否确认导出重粉?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        const loading = this.$loading({
+          lock: true,
+          text: '正在导出数据,请稍候...',
+          spinner: 'el-icon-loading',
+          background: 'rgba(0, 0, 0, 0.3)'
+        });
+
+        return exportFans(params)
+          .then(res => {
+            if (res.code !== 200){
+              this.$message.warning(res.msg);
+              return;
+            }
+
+            this.download(res.msg);
+          })
+
+          .finally(() => {
+            loading.close();
+          });
+      });
     }
-  }
+    },
+
 };
 </script>

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

@@ -371,6 +371,7 @@ export default {
         this.statistics.answerRightTimes += details.answerRightNum || 0;
         this.statistics.redPacketAmount += details.redPacketAmount || 0;
       });
+      this.statistics.redPacketAmount = this.statistics.redPacketAmount.toFixed(2);
 
       // 计算完播率
       if (this.statistics.courseWatchNum > 0) {

+ 80 - 9
src/views/course/videoResource/index.vue

@@ -20,9 +20,16 @@
         />
       </el-form-item>
       <el-form-item label="分类" prop="typeId">
-        <el-select v-model="queryParams.typeId" clearable placeholder="请选择分类" @change="val => changeCateType(val, 1)">
+        <el-select
+          v-model="queryParams.typeId"
+          clearable
+          placeholder="请选择或输入分类"
+          @change="val => changeCateType(val, 1)"
+          filterable
+          :filter-method="filterTypeOptions"
+          @visible-change="handleSelectVisibleChange">
           <el-option
-            v-for="item in rootTypeList"
+            v-for="item in filteredRootTypeList"
             :key="item.dictValue"
             :label="item.dictLabel"
             :value="item.dictValue">
@@ -193,7 +200,7 @@
     />
 
     <!-- 添加或修改视频素材库对话框 -->
-    <minimizable-dialog :title="title" :visible.sync="open" width="700px" append-to-body :before-close="cancel"
+    <el-dialog :title="title" :visible.sync="open" width="700px" append-to-body :before-close="cancel"
                         @minimize="hasMinimizableDialog = true" @restore="hasMinimizableDialog = false">
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="素材名称" prop="resourceName" style="margin-top: 20px">
@@ -338,7 +345,7 @@
           {{ isUploading ? '上传中...' : '保存' }}
         </el-button>
       </div>
-    </minimizable-dialog>
+    </el-dialog>
 
     <el-dialog
       title="视频预览"
@@ -353,7 +360,7 @@
     </el-dialog>
 
     <!--批量修改弹框-->
-    <minimizable-dialog :title="'批量修改'" :visible.sync="batchUpdateVisible" width="700px" append-to-body :before-close="cancel"
+    <el-dialog :title="'批量修改'" :visible.sync="batchUpdateVisible" width="700px" append-to-body :before-close="cancel"
                         @minimize="hasMinimizableDialog = true" @restore="hasMinimizableDialog = false">
       <el-form ref="form" :model="batchUpdateForm" :rules="rules" label-width="80px">
       <el-form-item label="分类" prop="typeId">
@@ -382,7 +389,7 @@
         <el-button @click="cancelBatch">取 消</el-button>
         <el-button type="primary" @click="submitBatchUpdate">保 存</el-button>
       </div>
-    </minimizable-dialog>
+    </el-dialog>
     <!-- 批量选择视频弹窗 -->
     <minimizable-dialog :title="'选择视频'" :visible.sync="batchAddVisible" width="1200px" append-to-body class="batch-dialog"
       :close-on-click-modal="false" :before-close="cancelBeforeBatch" @minimize="hasMinimizableDialog = true"
@@ -503,7 +510,21 @@
         </el-table-column>
         <el-table-column label="操作" align="center" width="150">
           <template slot-scope="scope">
-            <el-button size="mini" type="text" icon="el-icon-edit" @click="handleEditVideo(scope.row)">编辑</el-button>
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-edit"
+              :disabled="scope.row.progress !== 100"
+              @click="handleEditVideo(scope.row)">
+              编辑
+              <el-tooltip
+                v-if="scope.row.progress !== 100"
+                content="上传完成后可编辑"
+                placement="top"
+              >
+                <i class="el-icon-question"></i>
+              </el-tooltip>
+            </el-button>
             <el-button
               size="mini"
               type="text"
@@ -787,6 +808,8 @@ export default {
   },
   data() {
     return {
+      filteredRootTypeList: [], // 过滤后的分类列表
+      originalRootTypeList: [], // 原始分类列表
       // 遮罩层
       loading: true,
       // 选中数组
@@ -1176,9 +1199,53 @@ export default {
     },
     getRootTypeList() {
       getCatePidList().then(response => {
-        this.rootTypeList = response.data
+        this.rootTypeList = response.data;
+        this.originalRootTypeList = [...response.data]; // 保存原始数据
+        this.filteredRootTypeList = [...response.data]; // 初始化过滤列表
+      });
+    },
+    /** 过滤分类选项 */
+    filterTypeOptions(query) {
+      if (!query) {
+        this.filteredRootTypeList = [...this.originalRootTypeList];
+        return;
+      }
+
+      this.filteredRootTypeList = this.originalRootTypeList.filter(item => {
+        return item.dictLabel.toLowerCase().includes(query.toLowerCase()) ||
+          item.dictValue.toString().includes(query);
+      });
+    },
+    /** 选择后重置过滤 */
+    onTypeSelect(value) {
+      // 确保选中后恢复完整列表
+      this.filteredRootTypeList = [...this.originalRootTypeList];
+      // 触发 change 事件
+      this.changeCateType(value, 1);
+    },
+    /** 重置过滤 */
+    resetFilter() {
+      this.filteredRootTypeList = [...this.originalRootTypeList];
+    },
+
+    /** 清除过滤 */
+    clearFilter() {
+      this.$nextTick(() => {
+        if (!this.queryParams.typeId) {
+          this.filteredRootTypeList = [...this.originalRootTypeList];
+        }
       });
     },
+    /** 处理下拉框显示状态变化 */
+    handleSelectVisibleChange(visible) {
+      if (!visible) {
+        // 下拉框关闭时恢复完整列表
+        this.filteredRootTypeList = [...this.originalRootTypeList];
+      } else {
+        // 下拉框打开时也确保列表完整
+        this.filteredRootTypeList = [...this.originalRootTypeList];
+      }
+    },
     async changeCateType(val, type) {
       if (type === 1) {
         this.queryParams.typeSubId = null
@@ -1644,6 +1711,10 @@ export default {
     },
     /** 编辑视频信息 */
     handleEditVideo(row) {
+      if (row.progress !== 100) {
+        this.$message.warning('请等待上传完成后再编辑');
+        return;
+      }
       this.batchEditDialog.form = Object.assign({}, row)
       this.changeCateType(row.typeId)
       this.batchEditDialog.open = true
@@ -1912,7 +1983,7 @@ export default {
 
         // 检查上传结果
         const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
-        const line2Success = line1Result.status === 'fulfilled' && line1Result.value.success;
+        const line2Success = line2Result.status === 'fulfilled' && line2Result.value.success;
 
         const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
         if (index !== -1) {

+ 74 - 0
src/views/crm/components/addRemark.vue

@@ -0,0 +1,74 @@
+<template>
+    <div>
+            <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+                <el-form-item label="备注" prop="remark">
+                    <el-input :rows="2" v-model="form.remark" type="textarea" placeholder="请输入内容" />
+                </el-form-item>
+            </el-form>
+            <div  class="footer">
+                <el-button type="primary" @click="submitForm">确 定</el-button>
+            </div>
+    </div>
+</template>
+  
+<script>
+    import { updateCustomer  } from "@/api/crm/customer";
+    export default {
+        name: "remark",
+        data() {
+            return {
+                form: {
+                    customerId:null,
+                    remark:null,
+                 
+                },
+                // 表单校验
+                rules: {
+                    remark: [
+                        { required: true, message: "备注不能为空", trigger: "change" }
+                    ],
+         
+                }
+                 
+            };
+        },
+        created() {
+        },
+        methods: {
+            reset(customer) {
+                this.form.customerId=customer.customerId;
+                this.form.remark=customer.remark;
+            },
+            submitForm() {
+                this.$refs["form"].validate(valid => {
+                    if (valid) {
+                        updateCustomer(this.form).then(response => {
+                            if (response.code === 200) {
+                                this.msgSuccess("操作成功");
+                                
+                                this.$emit('close');
+                            }
+                        });
+                    }
+                });
+            },
+        }
+    };
+</script>
+<style lang="scss" scoped>
+.contents{
+    height: 100%;
+    background-color: #fff;
+    padding: 20px;
+        
+}
+.footer{
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+}
+</style>
+
+
+
+ 

+ 143 - 0
src/views/crm/components/addTag.vue

@@ -0,0 +1,143 @@
+<template>
+    <div>
+            <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+                <el-form-item label="标签库" >
+                    <el-select @change="tagChange" v-model="tag" placeholder="请选择" clearable size="small">
+                        <el-option
+                                v-for="item in tagsOptions"
+                                :key="item.dictValue"
+                                :label="item.dictLabel"
+                                :value="item.dictValue"
+                            />
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="标签" prop="tags">
+                    <el-tag
+                        :key="tag"
+                        v-for="tag in tags"
+                        closable
+                        :disable-transitions="false"
+                        @close="handleClose(tag)">
+                        {{tag}}
+                    </el-tag>
+                    <el-input
+                        class="input-new-tag"
+                        v-if="inputVisible"
+                        v-model="inputValue"
+                        ref="saveTagInput"
+                        size="small"
+                        @keyup.enter.native="handleInputConfirm"
+                        @blur="handleInputConfirm"
+                    >
+                    </el-input>
+                    <el-button v-else class="button-new-tag" size="small" @click="showInput">添加标签+</el-button>
+                </el-form-item>
+            </el-form>
+            <div  class="footer">
+                <el-button type="primary" @click="submitForm">确 定</el-button>
+            </div>
+    </div>
+</template>
+  
+<script>
+    import { updateCustomer  } from "@/api/crm/customer";
+    export default {
+        name: "visit",
+        data() {
+            return {
+                tag:null,
+                tagsOptions:[],
+                inputVisible: false,
+                inputValue: '',
+                tags:[],
+                form: {
+                    customerId:null,
+                    tags:null,
+                 
+                },
+                // 表单校验
+                rules: {
+                    tags: [
+                        { required: true, message: "标签不能为空", trigger: "change" }
+                    ],
+         
+                }
+                 
+            };
+        },
+        created() {
+            this.getDicts("crm_customer_tag").then((response) => {
+                this.tagsOptions = response.data;
+            });
+            
+            
+        },
+        methods: {
+            tagChange(e){
+                console.log(e)
+                var v=this.tagsOptions.find(value=>value.dictValue==e);
+                console.log(v)
+                this.tags.push(v.dictLabel);
+                this.form.tags=this.tags.toString();
+
+            },
+            reset(customer) {
+                this.tags= [];
+                this.form.customerId=customer.customerId;
+                this.form.tags=customer.tags;
+                if(this.form.tags!=null){
+                    this.tags=this.form.tags.split(",")
+                }
+            },
+            handleClose(tag) {
+                this.tags.splice(this.tags.indexOf(tag), 1);
+                this.form.tags=this.tags.toString();
+            },
+            showInput() {
+                this.inputVisible = true;
+                this.$nextTick(_ => {
+                    this.$refs.saveTagInput.focus();
+                });
+            },
+            handleInputConfirm() {
+                let inputValue = this.inputValue;
+                if (inputValue) {
+                    this.tags.push(inputValue);
+                }
+                this.inputVisible = false;
+                this.inputValue = '';
+                this.form.tags=this.tags.toString();
+            },
+            submitForm() {
+                this.$refs["form"].validate(valid => {
+                    if (valid) {
+                        updateCustomer(this.form).then(response => {
+                            if (response.code === 200) {
+                                this.msgSuccess("操作成功");
+                                
+                                this.$emit('close');
+                            }
+                        });
+                    }
+                });
+            },
+        }
+    };
+</script>
+<style lang="scss" scoped>
+.contents{
+    height: 100%;
+    background-color: #fff;
+    padding: 20px;
+        
+}
+.footer{
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+}
+</style>
+
+
+
+ 

+ 41 - 13
src/views/crm/externalContact/index.vue

@@ -20,6 +20,15 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
+      <el-form-item label="客户名称(准)" prop="preciseName">
+        <el-input
+          v-model="queryParams.preciseName"
+          placeholder="请输入客户名称精确查询"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
       <el-form-item label="活码id" prop="wayId">
         <el-input
           v-model="queryParams.wayId"
@@ -360,7 +369,7 @@
 <!--          </div>-->
 <!--        </template>-->
         <template slot-scope="scope">
-          <div class="tag-container">
+          <div class="table-tag-container">
             <div class="tag-list">
               <el-tag
                 v-for="name in scope.row.tagIdsName"
@@ -529,6 +538,8 @@
 
       <div>搜索标签:
         <el-input v-model="queryTagParams.name" placeholder="请输入标签名称" clearable size="small" style="width: 200px;margin-right: 10px" />
+        搜索标签(精确):
+        <el-input v-model="queryTagParams.preciseName" placeholder="请输入精确标签名称" clearable size="small" style="width: 200px;margin-right: 10px" />
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleSearchTags(queryTagParams.name)">搜索</el-button>
         <el-button type="primary" icon="el-icon-plus" size="mini" @click="cancelSearchTags">重置</el-button>
       </div>
@@ -787,18 +798,13 @@ import {
   setCustomerCourseSopList,
   unBindUserId, updateExternalContactCall
 } from '@/api/qw/externalContact'
-/*import {getMyQwUserList, getMyQwCompanyList, updateUser,getQwUserListLikeName} from "@/api/qw/user";*/
-import {getMyQwUserList, getMyQwCompanyList, updateUser} from "@/api/qw/user";
-import {listTag, getTag, searchTags,} from "@/api/qw/tag";
+
+import { getMyQwCompanyListAll } from '@/api/qw/user'
+import {listTag, searchTags,} from "@/api/qw/tag";
 import { allListTagGroup} from "@/api/qw/tagGroup";
-/*import mycustomer from '@/views/qw/externalContact/mycustomer'
-import customerDetails from '@/views/qw/externalContact/customerDetails'
-import SopDialog from '@/views/course/sop/SopDialog.vue'
-import  selectUser  from "@/views/qw/externalContact/selectUser.vue";
-import info from "@/views/qw/externalContact/info.vue";
-import { editTalk } from "@/api/qw/externalContactInfo";*/
+
 import PaginationMore from "../../../components/PaginationMore/index.vue";
-/*import userDetails from '@/views/store/components/userDetails.vue';*/
+
 export default {
   name: "ExternalContact",
   /*components:{PaginationMore, mycustomer,customerDetails,SopDialog,selectUser,info,userDetails},*/
@@ -939,6 +945,7 @@ export default {
         pageSize: 5,
         total:0,
         name:null,
+        preciseName:null,
         corpId:null,
       },
 
@@ -952,6 +959,7 @@ export default {
         qwUserName:null,
         externalUserId: null,
         name: null,
+        preciseName: null,
         avatar: null,
         type: null,
         qwUserId:null,
@@ -1004,7 +1012,7 @@ export default {
     });
 
 
-    getMyQwCompanyList().then(response => {
+    getMyQwCompanyListAll().then(response => {
             this.myQwCompanyList = response.data;
             if(this.myQwCompanyList!=null){
               // this.queryParams.corpId=this.myQwCompanyList[0].dictValue
@@ -1267,6 +1275,7 @@ export default {
       );
 
       this.queryTagParams.name=null;
+      this.queryTagParams.preciseName=null;
 
       this.getPageListTagGroup();
 
@@ -1506,7 +1515,7 @@ export default {
     },
     handleSearchTags(name){
 
-      if (!name){
+      if (!name && (this.queryTagParams.preciseName == null || this.queryTagParams.preciseName =="")){
         return this.$message.error("请输入要搜索的标签")
       }
 
@@ -2022,6 +2031,25 @@ export default {
   border-radius: 1px;
   background-color: #fafafa;
 }
+.table-tag-container {
+  max-height: 72px;
+  overflow-y: auto;
+  padding: 4px;
+}
+.table-tag-container::-webkit-scrollbar {
+  width: 6px;
+}
+.table-tag-container::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+.table-tag-container::-webkit-scrollbar-thumb {
+  background: #c0c4cc;
+  border-radius: 3px;
+}
+.table-tag-container::-webkit-scrollbar-thumb:hover {
+  background: #909399;
+}
 .tag-list {
   display: flex;
   flex-wrap: wrap;

+ 294 - 0
src/views/fastGpt/fastGptChatMsg/index.vue

@@ -0,0 +1,294 @@
+<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="roleId">
+        <el-select v-model="queryParams.roleId" placeholder="请选择客服角色" clearable size="small">
+          <el-option
+                v-for="item in roles"
+                :key="item.roleId"
+                :label="item.roleName"
+                :value="item.roleId"
+              />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="消息类型" prop="msgType">
+          <el-select v-model="queryParams.msgType" placeholder="请选择类型" clearable size="small">
+           <el-option
+             v-for="item in typeOptions"
+               :key="item.dictValue"
+               :label="item.dictLabel"
+               :value="item.dictValue"
+             />
+          </el-select>
+      </el-form-item>
+
+      <el-form-item label="发送时间" prop="createTime">
+        <el-date-picker
+           style="width:220px"
+           clearable size="small"
+           v-model="dateRange"
+           type="daterange"
+           value-format="yyyy-MM-dd"
+           start-placeholder="开始日期"
+           end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['chat:chatMsg:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="chatMsgList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="msgId" />
+      <el-table-column label="用户" align="center" prop="nickName" />
+      <el-table-column label="头像" width="150" align="center">
+        <template slot-scope="scope">
+          <img :src="scope.row.avatar" style="height: 80px">
+        </template>
+      </el-table-column>
+      <el-table-column label="AI客服" align="center" prop="roleName" />
+      <el-table-column show-overflow-tooltip label="消息内容" align="center" prop="content" />
+      <el-table-column  label="消息类型"  align="center" prop="msgType">
+            <template slot-scope="scope">
+                <el-tag prop="type" v-for="(item, index) in typeOptions"    v-if="scope.row.msgType==item.dictValue">{{item.dictLabel}}</el-tag>
+              </template>
+      </el-table-column>
+      <el-table-column  label="发送方式"  align="center" prop="sendType">
+            <template slot-scope="scope">
+                <el-tag prop="type" v-for="(item, index) in sendTypeOptions"    v-if="scope.row.sendType==item.dictValue">{{item.dictLabel}}</el-tag>
+            </template>
+      </el-table-column>
+
+      <el-table-column label="发送时间" align="center" prop="createTime" />
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listFastGptChatMsg, getFastGptChatMsg, delFastGptChatMsg, addFastGptChatMsg, updateFastGptChatMsg, exportFastGptChatMsg } from "@/api/fastGpt/fastGptChatMsg";
+import { getAllRoleList } from "@/api/fastGpt/fastGptRole";
+
+export default {
+  name: "ChatMsg",
+  data() {
+    return {
+      show:{
+              title:"聊天详情",
+              open:false,
+            },
+      roles:[],
+      dateRange:[],
+      typeOptions:[],
+      sendTypeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 聊天消息记录表格数据
+      chatMsgList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        chatId: null,
+        content: null,
+        msgType: null,
+        sendType: null,
+        companyId: null,
+        roleId: null,
+        companyUserId: null,
+        msgJson: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getDicts("chat_msg_type").then((response) => {
+      this.typeOptions = response.data;
+    });
+    this.getDicts("chat_msg_send_type").then((response) => {
+      this.sendTypeOptions = response.data;
+    });
+    getAllRoleList().then(response => {
+        this.roles = response.data;
+    });
+    this.getList();
+  },
+  methods: {
+    handleShow(){
+
+    },
+    /** 查询聊天消息记录列表 */
+    getList() {
+      this.loading = true;
+      listFastGptChatMsg(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
+        this.chatMsgList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        msgId: null,
+        chatId: null,
+        content: null,
+        msgType: null,
+        sendType: null,
+        companyId: null,
+        roleId: null,
+        companyUserId: null,
+        createTime: null,
+        msgJson: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.dateRange=null
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.msgId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加聊天消息记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const msgId = row.msgId || this.ids
+      getFastGptChatMsg(msgId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改聊天消息记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.msgId != null) {
+            updateFastGptChatMsg(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addFastGptChatMsg(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const msgIds = row.msgId || this.ids;
+      this.$confirm('是否确认删除聊天消息记录编号为"' + msgIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delFastGptChatMsg(msgIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有聊天消息记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportFastGptChatMsg(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>
+
+<style lang="css">
+  .el-tooltip__popper{
+    max-width:40%
+  }
+</style>

+ 200 - 0
src/views/fastGpt/fastGptChatSession/fastGptChatMsgDetails.vue

@@ -0,0 +1,200 @@
+<template>
+    <div style="background-color: #f0f2f5; padding-bottom: 20px; min-height: 100%; " >
+      <div style="padding: 20px; background-color: #fff;">
+         查看聊天记录
+      </div>
+      <div class="contentx" v-if="item!=null">
+          <div class="desct"> 会话详情</div>
+          <el-descriptions title="" :column="3" border>
+            <el-descriptions-item label="会话标识"><span v-if="item!=null">{{item.chatId}}</span></el-descriptions-item>
+            <el-descriptions-item label="咨询客户"><span v-if="item!=null">{{item.nickName}}</span></el-descriptions-item>
+            <el-descriptions-item label="客服账号" span="3"><span v-if="item!=null">{{item.roleName}}</span></el-descriptions-item>
+            <el-descriptions-item label="接待时间"><span v-if="item!=null">{{item.createTime}}</span></el-descriptions-item>
+            <el-descriptions-item label="结束时间"><span v-if="item!=null">{{item.updateTime}}</span></el-descriptions-item>
+            <el-descriptions-item label="服务状态">
+              <span v-if="item!=null"><dict-tag :options="sessionStatusOptions" :value="item.status"/> </span>
+            </el-descriptions-item>
+          </el-descriptions>
+      </div>
+      <div class="contentx" v-if="item!=null">
+          <div class="desct"> 查看聊天记录</div>
+          <div class="block">
+            <el-timeline>
+              <el-timeline-item v-for="(i, index) in msgList" :key="index" :timestamp="i.createTime" placement="top">
+                <el-card>
+                  <h4 v-if="i.sendType==1">{{ item.nickName }}</h4>
+                  <h4 v-else>{{item.roleName}}</h4>
+                  <p>{{i.content}}</p>
+                 <!-- <p v-if="i.sendType==2" style="color: #c8cacb;">{{i.status==1?'正确':'未标记'}}</p>
+                  <el-link type="primary" :underline="false" v-if="i.sendType==2" class="ivu-pl-8" @click="updateLogs(i.msgId)" style="float: right; margin-right: 20px; margin-bottom: 20px;">修改记录</el-link>
+                  <el-link type="primary" :underline="false" v-if="i.sendType==2" class="ivu-pl-8" @click="updateMsgStatus(i.msgId)" style="float: right; margin-right: 20px; ">√</el-link>
+                  <el-link type="primary" :underline="false" v-if="i.sendType==2" class="ivu-pl-8" @click="updateMsg(i.msgId)" style="float: right; margin-right: 20px; ">X</el-link> -->
+                </el-card>
+              </el-timeline-item>
+            </el-timeline>
+          </div>
+      </div>
+    <!-- 添加或修改聊天消息记录对话框 -->
+    <el-dialog title="修改聊天消息" :visible.sync="open" width="700px" append-to-body>
+      <el-form ref="form" :model="form"  label-width="80px">
+        <el-form-item label="消息内容">
+          <el-input type="textarea" v-model="form.content" :rows="12"/>
+        </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-dialog title="修改记录" :visible.sync="logsOpen" width="800px" append-to-body>
+      <el-timeline>
+        <el-timeline-item v-for="(i, index) in chatMsgLogsList" :key="index" :timestamp="i.createTime" placement="top">
+          <el-card>
+            <h4 v-if="i.logsType==1">标记正确</h4>
+            <h4 v-else>修改回复</h4>
+            <p style="float: right;">{{i.nickName}}</p>
+            <p>{{i.content}}</p>
+          </el-card>
+        </el-timeline-item>
+      </el-timeline>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="logsCancel">确 定</el-button>
+      </div>
+    </el-dialog>
+
+
+    </div>
+</template>
+
+<script>
+import { getFastGptChatMsg, updateFastGptChatMsg } from "@/api/fastGpt/fastGptChatMsg";
+
+import { getFastGptChatSession } from "@/api/fastGpt/fastGptChatSession";
+import { getAllRoleList } from "@/api/fastGpt/fastGptRole";
+import { listFastGptChatMsgLogs} from "@/api/fastGpt/fastGptChatMsgLogs";
+  export default {
+    name: "fastGptChatMsgDetails",
+    data() {
+      return {
+        sessionStatusOptions:[],
+        open:false,
+        logsOpen:false,
+        // 方剂类型字典
+        typeOptions:[],
+        sendTypeOptions:[],
+        roles:[],
+        msgList:[],
+        chatMsgLogsList:[],
+        // 状态字典
+        statusOptions: [],
+        item:null,
+        form: {},
+      }
+    },
+    created() {
+      this.getDicts("sys_chat_session_status").then(response => {
+        this.sessionStatusOptions = response.data;
+      });
+      this.getDicts("chat_msg_type").then((response) => {
+        this.typeOptions = response.data;
+      });
+      this.getDicts("chat_msg_send_type").then((response) => {
+        this.sendTypeOptions = response.data;
+      });
+      getAllRoleList().then(response => {
+          this.roles = response.data;
+      });
+    },
+    methods: {
+      getDetails(id) {
+        this.item=null;
+        getFastGptChatSession(id).then(response => {
+            this.item = response.data;
+            this.msgList = response.list;
+        });
+      },
+      updateMsg(row){
+         this.form = null;
+          getFastGptChatMsg(row).then(response => {
+          this.open = true;
+          this.form = response.data;
+        });
+      },
+      updateMsgStatus(row){
+        var statusForm={
+          msgId:row,
+          status:1
+        }
+        this.$confirm('是否确认标记?', "警告", {
+            confirmButtonText: "确定",
+            cancelButtonText: "取消",
+            type: "warning"
+          }).then(() => {
+            this.exportLoading = true;
+            return updateFastGptChatMsg(statusForm);
+          }).then(response => {
+            this.msgSuccess("修改成功");
+            this.open = false;
+          var status=  this.msgList.find(item => item.msgId ==row);
+          status.status=1;
+          }).catch(() => {});
+
+
+      },
+      updateLogs(row){
+        this.logsOpen=true;
+        var queryParams= {
+          msgId: row
+        }
+        listFastGptChatMsgLogs(queryParams).then(response => {
+          this.chatMsgLogsList = response.rows;
+        });
+      },
+      logsCancel(){
+        this.logsOpen=false;
+      },
+      submitForm(){
+        var submitForm={
+          msgId:this.form.msgId,
+          content:this.form.content
+        }
+        updateFastGptChatMsg(submitForm).then(response => {
+          this.msgSuccess("修改成功");
+          this.open = false;
+          var status=  this.msgList.find(item => item.msgId ==this.form.msgId);
+          status.content=this.form.content;
+        });
+      },
+      cancel(){
+          this.open = false;
+      }
+    }
+  }
+</script>
+<style>
+  .contentx{
+      height: 100%;
+      background-color: #fff;
+      padding: 0px 20px 20px;
+
+
+      margin: 20px;
+  }
+  .el-descriptions-item__label.is-bordered-label{
+    font-weight: normal;
+  }
+  .el-descriptions-item__content {
+    max-width: 150px;
+    min-width: 100px;
+  }
+  .desct{
+      padding-top: 20px;
+      padding-bottom: 20px;
+      color: #524b4a;
+      font-weight: bold;
+    }
+  .padding-a{
+    padding-right: 10px;
+
+  }
+</style>

+ 480 - 0
src/views/fastGpt/fastGptChatSession/index.vue

@@ -0,0 +1,480 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="会话标识" prop="chatId">
+        <el-input
+          v-model="queryParams.chatId"
+          placeholder="请输入会话标识"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="客户昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入客户昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+	  <el-form-item label="角色昵称" prop="roleName">
+	    <el-input
+	      v-model="queryParams.roleName"
+	      placeholder="请输入角色昵称"
+	      clearable
+	      size="small"
+	      @keyup.enter.native="handleQuery"
+	    />
+	  </el-form-item>
+	  <el-form-item label="企微账号" prop="qwUserName">
+	    <el-input
+	      v-model="queryParams.qwUserName"
+	      placeholder="请输入企微账号"
+	      clearable
+	      size="small"
+	      @keyup.enter.native="handleQuery"
+	    />
+	  </el-form-item>
+      <el-form-item label="会话状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option
+            v-for="item in sessionStatusOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+		<el-form-item label="是否人工" prop="isArtificial">
+		  <el-select v-model="queryParams.isArtificial" placeholder="请选择状态" clearable size="small">
+		    <el-option
+		      v-for="item in orOptions"
+		      :key="item.dictValue"
+		      :label="item.dictLabel"
+		      :value="item.dictValue"
+		    />
+		  </el-select>
+		</el-form-item>
+      <el-form-item label="查看状态" prop="isLook">
+        <el-select v-model="queryParams.isLook" placeholder="请选择" clearable size="small">
+          <el-option
+            v-for="item in sessionLookOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="接待时间" prop="createTime">
+        <el-date-picker
+          style="width:220px"
+          clearable size="small"
+          v-model="dateRange"
+          type="daterange"
+          value-format="yyyy-MM-dd"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+          @change="changeTime"
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="primary"-->
+<!--          plain-->
+<!--          icon="el-icon-plus"-->
+<!--          size="mini"-->
+<!--          @click="handleAdd"-->
+<!--          v-hasPermi="['fastGpt:fastGptChatSession: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="['fastGpt:fastGptChatSession: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="['fastGpt:fastGptChatSession: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="['fastGpt:fastGptChatSession:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" border :data="fastGptChatSessionList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="会话标识" align="center" prop="chatId" />
+      <el-table-column label="咨询客户" align="center" prop="nickName" />
+      <el-table-column label="头像" align="center" prop="avatar" >
+        <template slot-scope="scope">
+          <el-image
+            v-if="scope.row.avatar != null"
+            :src="scope.row.avatar"
+            :preview-src-list="[scope.row.avatar]"
+            :style="{ width: '100px', height: '100px' }"
+          ></el-image>
+        </template>
+      </el-table-column>
+      <el-table-column label="角色昵称" align="center" prop="roleName" />
+	  <el-table-column label="企微账号" align="center" prop="qwUserName" />
+	  
+      <el-table-column label="接待时间" align="center" prop="createTime" >
+        <template slot-scope="scope">
+          <span v-if="!scope.row.updateTime && scope.row.status === 1">
+            {{ scope.row.createTime }} - 未结束
+          </span>
+          <span v-else>
+            {{ scope.row.createTime }} - {{ scope.row.updateTime }}
+          </span>
+        </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 sessionStatusOptions"    v-if="scope.row.status==item.dictValue">{{item.dictLabel}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="查看状态" align="center" prop="isLook" >
+        <template slot-scope="scope">
+          <el-tag prop="isLook" v-for="(item, index) in sessionLookOptions"    v-if="scope.row.isLook==item.dictValue">{{item.dictLabel}}</el-tag>
+        </template>
+      </el-table-column>
+	  <el-table-column label="是否人工" align="center" prop="isArtificial" >
+	    <template slot-scope="scope">
+	      <el-tag prop="isArtificial" v-for="(item, index) in orOptions"    v-if="scope.row.isArtificial==item.dictValue">{{item.dictLabel}}</el-tag>
+	    </template>
+	  </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleDetails(scope.row)"
+          >查看</el-button>
+		  <el-button v-if="scope.row.isArtificial==0"
+		    size="mini"
+		    type="text"
+		    @click="Artificial(scope.row,1)"
+		  >转人工</el-button>
+		  <el-button v-if="scope.row.isArtificial==1"
+		    size="mini"
+		    type="text"
+		    @click="Artificial(scope.row,0)"
+		  >转Ai</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="聊天id" prop="chatId">-->
+<!--          <el-input v-model="form.chatId" placeholder="请输入聊天id" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="客户ID uid" prop="userId">-->
+<!--          <el-input v-model="form.userId" placeholder="请输入客户ID uid" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="客服ID 应用id?" prop="kfId">-->
+<!--          <el-input v-model="form.kfId" placeholder="请输入客服ID 应用id?" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="状态 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="公司ID" prop="companyId">-->
+<!--          <el-input v-model="form.companyId" placeholder="请输入公司ID" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="是否查看" prop="isLook">-->
+<!--          <el-input v-model="form.isLook" placeholder="请输入是否查看" />-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="用户类型 1微信用户 2小程序用户 3销售用户" prop="userType">-->
+<!--          <el-select v-model="form.userType" placeholder="请选择用户类型 1微信用户 2小程序用户 3销售用户">-->
+<!--            <el-option label="请选择字典生成" value="" />-->
+<!--          </el-select>-->
+<!--        </el-form-item>-->
+<!--        <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>-->
+<!--      <div slot="footer" class="dialog-footer">-->
+<!--        <el-button type="primary" @click="submitForm">确 定</el-button>-->
+<!--        <el-button @click="cancel">取 消</el-button>-->
+<!--      </div>-->
+<!--    </el-dialog>-->
+    <el-drawer
+      :with-header="false"
+      size="75%"  @close="handleDrawerClose"
+      :title="show.title" :visible.sync="show.open">
+      <fastGptChatMsgDetails  ref="Details" />
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import { listFastGptChatSession, getFastGptChatSession, delFastGptChatSession, addFastGptChatSession, updateFastGptChatSession, exportFastGptChatSession } from "@/api/fastGpt/fastGptChatSession";
+import fastGptChatMsgDetails from "@/views/fastGpt/fastGptChatSession/fastGptChatMsgDetails.vue";
+
+export default {
+  name: "FastGptChatSession",
+  components: {fastGptChatMsgDetails},
+  data() {
+    return {
+      show:{
+        title:"聊天详情",
+        open:false,
+      },
+      //接待时间
+      dateRange:[],
+      //会话状态
+      sessionStatusOptions:[],
+      //查看状态
+      sessionLookOptions:[],
+	  orOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 对话关系表格数据
+      fastGptChatSessionList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        chatId: null,
+        userId: null,
+        kfId: null,
+        status: null,
+        companyId: null,
+        isLook: null,
+        userType: null,
+        nickName: null,
+        avatar: null,
+		qwUserName: null,
+		roleName: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getDicts("sys_chat_session_status").then(response => {
+      this.sessionStatusOptions = response.data;
+    });
+    this.getDicts("sys_chat_session_look").then(response => {
+      this.sessionLookOptions = response.data;
+    });
+	this.getDicts("sys_company_or").then(response => {
+	  this.orOptions = response.data;
+	});
+    this.getList();
+  },
+  methods: {
+	  Artificial(row,id){
+		  this.form.sessionId=row.sessionId
+		  this.form.isArtificial=id
+		  updateFastGptChatSession(this.form).then(response => {
+		    this.msgSuccess("修改成功");
+		    this.open = false;
+		    this.getList();
+		  });
+	  },
+    /** 查询对话关系列表 */
+    getList() {
+      this.loading = true;
+      listFastGptChatSession(this.queryParams).then(response => {
+        this.fastGptChatSessionList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    //查看按钮
+    handleDetails(row){
+      this.show.open=true;
+      setTimeout(() => {
+        this.$refs.Details.getDetails(row.sessionId);
+      }, 500);
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        sessionId: null,
+        chatId: null,
+        userId: null,
+        kfId: null,
+        createTime: null,
+        updateTime: null,
+        status: 0,
+        companyId: null,
+        isLook: null,
+        userType: null,
+        nickName: null,
+        avatar: null
+      };
+      this.resetForm("form");
+    },
+    //关闭
+    handleDrawerClose(){
+      this.getList();
+    },
+    /**
+    * 会话时间
+    */
+    changeTime(){
+      if(this.dateRange!=null){
+        this.queryParams.beginTime=this.dateRange[0];
+        this.queryParams.endTime=this.dateRange[1];
+      }else{
+        this.queryParams.beginTime=null;
+        this.queryParams.endTime=null;
+      }
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.sessionId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加对话关系";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const sessionId = row.sessionId || this.ids
+      getFastGptChatSession(sessionId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改对话关系";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.sessionId != null) {
+            updateFastGptChatSession(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addFastGptChatSession(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const sessionIds = row.sessionId || this.ids;
+      this.$confirm('是否确认删除对话关系编号为"' + sessionIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delFastGptChatSession(sessionIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有对话关系数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportFastGptChatSession(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 263 - 0
src/views/fastGpt/fastGptPushTokenTotalDept/index.vue

@@ -0,0 +1,263 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="公司名称" prop="companyId" >
+        <el-select v-model="queryParams.companyId" placeholder="请选择所属公司" filterable size="small">
+          <el-option v-for="(option, index) in companyList" :key="index" :value="option.dictValue" :label="option.dictLabel"></el-option>
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"  @change="changeTime"></el-date-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="fastGptPushTokenTotalList" :row-class-name="() => 'fixed-bottom-row'"
+              @selection-change="handleSelectionChange" :max-height="600">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="公司名称" align="center" prop="companyName" />
+      <el-table-column label="部门名称" align="center" prop="deptName" >
+        <template slot-scope="scope">
+          <!-- 简化判断逻辑,同时处理 null/undefined/空字符串 等所有空值情况 -->
+          <div>{{ scope.row.deptName || '--' }}</div>
+        </template>
+      </el-table-column>
+      <el-table-column label="token消耗" align="center" prop="count" />
+      <el-table-column label="时间" align="center" prop="statTime" />
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </div>
+</template>
+
+<script>
+import { listFastgptEventLogTotal, exportFastgptEventLogTotal, getFastGptRoleAppKeyList} from "@/api/fastGpt/fastgptEventLogTotal";
+import { getFastGptPushTokenTotal,getFastGptPushTokenDeptTotal} from "@/api/fastGpt/fastgptPushTokenTotal";
+import SelectTree from "@/components/TreeSelect/index.vue";
+import {getDeptData} from "@/api/system/employeeStats";
+import TreeSelect from '@riophae/vue-treeselect'
+import '@riophae/vue-treeselect/dist/vue-treeselect.css'
+import {allList} from "@/api/company/company";
+import Template from '../../his/complaint/template.vue';
+export default {
+  name: "FastgptEventLogTotal",
+  components: {SelectTree,TreeSelect},
+  data() {
+    return {
+      createTime:null,
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      selectedCompanyList: [],
+      companyList:[],
+      deptList: [],
+      companyUserList: [],
+      selectedAppKey: null,
+      selectedAppKeyLabel: '',
+      appKeyOptions: [],
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // token统计表格数据
+      fastGptPushTokenTotalList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      typeCountMap: null,
+      // 日志类型字典
+      typeOptions: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        roleId: null,
+        count: null,
+        type: null,
+        companyId: null,
+        companyUserId: null,
+        qwUserId: null,
+        typeCountMap: null,
+        beginTime:null,
+        endTime:null,
+        appKey:null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.getAllCompany();
+
+  },
+
+  methods: {
+    getAllCompany() {
+      allList().then(response => {
+        this.companyList = response.rows;
+        this.companyList.push({dictLabel: "无",
+          dictValue: "0"})
+      });
+    },
+    /** 查询ai事件埋点统计列表 */
+    getList() {
+      this.loading = true;
+
+      getFastGptPushTokenDeptTotal(this.queryParams).then(response => {
+        console.log(response)
+        this.fastGptPushTokenTotalList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    normalizer(node) {
+      return {
+        id: node.id,
+        label: node.label,
+        children: node.children,
+        disabled: node.disabled
+      }
+    },
+    handleAppKeyChange(value) {
+      const node = this.findNodeById(this.appKeyOptions, value);
+      if (!node) {
+        this.selectedAppKeyLabel = '';
+        return;
+      }
+
+      // 如果是子节点,则找父节点 label
+      if (node.parentLabel) {
+        this.queryParams.appKey = node.parentLabel;
+        this.selectedAppKeyLabel = node.parentLabel;
+        this.selectedAppKey = this.selectedAppKeyLabel;
+      } else {
+        this.queryParams.appKey = node.label;
+        this.selectedAppKeyLabel = node.label;
+        this.selectedAppKey = this.selectedAppKeyLabel;
+      }
+    },
+    findNodeById(nodes, id) {
+      for (const node of nodes) {
+        if (node.id === id) return node;
+        if (node.children) {
+          const found = this.findNodeById(node.children, id);
+          if (found) return found;
+        }
+      }
+      return null;
+    },
+    changeTime(){
+      console.log(this.createTime);
+      if(this.createTime!=null){
+        this.queryParams.beginTime=this.createTime[0];
+        this.queryParams.endTime=this.createTime[1];
+      }else{
+        this.queryParams.beginTime=null;
+        this.queryParams.endTime=null;
+      }
+      console.log(this.queryParams.beginTime);
+      console.log(this.queryParams.endTime);
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        roleId: null,
+        count: null,
+        type: null,
+        companyId: null,
+        companyUserId: null,
+        qwUserId: null,
+        statTime: null
+      };
+      this.resetForm("form");
+    },
+    getPercentageColor(percentage) {
+      // HSL模式从黄色(60度)渐变到红色(0度)
+      const percent = Math.min(100, Math.max(0, parseFloat(percentage)));
+
+      // 调整色相范围:从深黄色(40°)渐变到红色(0°)
+      const hue = 40 - 40 * (percent / 100); // 初始 hue=40(深黄),终点 hue=0(红)
+
+      // 提高饱和度(100%),亮度保持 50%(鲜艳但不刺眼)
+      return `hsl(${hue}, 100%, 50%)`;
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      console.log(this.selectedAppKey)
+      if(this.selectedAppKey === null || typeof this.selectedAppKey === 'undefined'){
+        this.queryParams.appKey = null;
+      }
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.selectedAppKey = null;
+      this.selectedAppKeyLabel = '';
+      this.selectedCompanyList = [];
+      this.queryParams.appKey = null;
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+
+      if(this.selectedCompanyList != null && this.selectedCompanyList.length > 0) {
+        this.queryParams.userIds = this.selectedCompanyList;
+      }else {
+        this.queryParams.userIds = [];
+      }
+      this.$confirm('是否确认导出所有ai事件埋点统计数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportFastgptEventLogTotal(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 239 - 0
src/views/fastGpt/fastGptRole/fastGptRole.vue

@@ -0,0 +1,239 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="客服名称" prop="roleName">
+        <el-input
+          v-model="queryParams.roleName"
+          placeholder="请输入客服名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+
+    <el-table v-loading="loading" :data="fastGptRoleList">
+      <el-table-column label="ID" align="center" prop="roleId" />
+      <el-table-column label="客服名称" align="center" prop="roleName" />
+      <el-table-column label="客服类型" align="center" prop="roleType" >
+        <template slot-scope="scope">
+          <el-tag prop="type" v-for="(item, index) in typeOptions"    v-if="scope.row.roleType==item.dictValue">{{item.dictLabel}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="客服应用头像" align="center" prop="avatar" >
+        <template slot-scope="scope">
+          <img :src="scope.row.avatar" style="height: 80px">
+        </template>
+      </el-table-column>
+      <el-table-column label="提示词" align="center" prop="reminderWords" >
+        <template slot-scope="scope">
+          <el-tooltip class="item" effect="dark" :content="scope.row.reminderWords" placement="top">
+            <div style="display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; text-overflow: ellipsis;">
+              <span>{{ scope.row.reminderWords }}</span>
+            </div>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+      <el-table-column label="绑定的公司" align="center" prop="corpName" />
+      <el-table-column label="操作" align="center">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="primary"
+            plain
+            icon="el-icon-edit"
+            @click="handleBindAi(scope.row)"
+          >绑定AI客服</el-button>
+
+<!--          <el-button
+            style="margin-top: 2%"
+            v-if="scope.row.corpName!=null"
+            size="mini"
+            type="primary"
+            plain
+            icon="el-icon-edit"
+            @click="handleRelieveAi(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 { newListFastGptRole, getFastGptRole, delFastGptRole, addFastGptRole, updateFastGptRole, exportFastGptRole,relieveFastGptRole } from "@/api/fastGpt/fastGptRole";
+import {BindAi, qwUserBindAi} from "../../../api/qw/user";
+
+export default {
+  name: "FastGptRole",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      //AI客服类型
+      typeOptions:[],
+      qwUserId:null,
+      corpId:null,
+      companyId:null,
+      //AI模型
+      modeOptions:[],
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 应用表格数据
+      fastGptRoleList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        roleName: null,
+        companyId: null,
+        roleType: null,
+        mode: null,
+        kfId: null,
+        kfUrl: null,
+        avatar: null,
+        kfMediaId: null,
+        bindCorpId: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        roleName: [
+          { required: true, message: "角色名称不能为空", trigger: "blur" }
+        ],
+        roleType: [
+          { required: true, message: "角色类型不能为空", trigger: "change" }
+        ],
+      }
+    };
+  },
+  created() {
+    //客服类型
+    this.getDicts("chat_role_type").then((response) => {
+      this.typeOptions = response.data;
+    });
+    this.getList();
+  },
+  methods: {
+    /** 查询应用列表 */
+    getList() {
+      this.loading = true;
+      this.queryParams.bindCorpId = this.corpId;
+      newListFastGptRole(this.queryParams).then(response => {
+        this.fastGptRoleList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+
+    handleRelieveAi(row){
+
+      if (row.corpId!==this.corpId){
+        return this.$message.error("当前企业无法解绑 其他企业 ");
+      }
+
+      this.$confirm('此处解绑-会将【所有】已绑定此应用的员工 的状态清除!确定要解除 应用:"' + row.roleId +"("+ row.roleName +')"的绑定?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return relieveFastGptRole(row.roleId);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("解绑成功");
+      }).catch(() => {});
+    },
+
+    // 表单重置
+    reset() {
+      this.form = {
+        roleId: null,
+        roleName: null,
+        companyId: null,
+        createTime: null,
+        updateTime: null,
+        roleType: null,
+        modeConfigJson:{APPKey:null,Key:null},
+        mode: null,
+        kfId: null,
+        kfUrl: null,
+        avatar: null,
+        kfMediaId: null,
+        reminderWords: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+
+    //重置参数
+    resetParam(){
+      this.qwUserId = null;
+      this.corpId = null;
+      this.getList();
+    },
+
+    handleBindAi(row){
+      qwUserBindAi({id:this.qwUserId,fastGptRoleId:row.roleId,corpId:this.corpId}).then(res => {
+        this.$message.success(res.msg);
+        this.resetParam();
+
+        this.$emit("refreshFastGptList");
+
+      });
+    },
+
+    handleBindAiData(val){
+      this.qwUserId=val.id;
+      this.corpId=val.corpId;
+      this.queryParams.bindCorpId = val.corpId;
+      this.queryParams.companyId = val.companyId;
+      // 设置完数据后主动查询
+      this.getList();
+    }
+  }
+};
+</script>

+ 15 - 0
src/views/fastGpt/fastgptEventLogTotal/index.vue

@@ -55,6 +55,15 @@
     </el-form>
 
     <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          icon="el-icon-download"
+          size="mini"
+          @click="handleExport"
+          v-hasPermi="['fastGpt:fastgptEventLogTotal:export']"
+        >导出</el-button>
+      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
@@ -77,6 +86,12 @@
     </span>
         </template>
       </el-table-column>
+      <!-- 新增金额列 -->
+      <el-table-column label="金额" align="center">
+        <template slot-scope="scope">
+          <span>{{ scope.row.typeCountMap && scope.row.typeCountMap['11'] ? (scope.row.typeCountMap['11'] / 100000).toFixed(4) : '0.0000' }}</span>
+        </template>
+      </el-table-column>
     </el-table>
 
     <pagination

+ 1 - 1
src/views/his/adv/index.vue

@@ -206,7 +206,7 @@
        <el-form-item label="文章内容" v-show="form.showType==3">
          <editor ref="myeditor" @on-text-change="updateText" />
        </el-form-item>
-        <el-form-item label="活动" prop="activeId" v-show="form.showType === '5'">
+        <el-form-item label="活动" prop="activeId" v-if="form.showType === '5'">
           <el-select v-model="form.activeId"  placeholder="请选择活动" >
             <el-option
               v-for="item in activeOptions"

+ 181 - 0
src/views/his/aiWorkflow/design.scss

@@ -0,0 +1,181 @@
+.workflow-designer {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background: #f5f5f5;
+
+  .toolbar {
+    height: 50px;
+    background: #fff;
+    border-bottom: 1px solid #e8e8e8;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 16px;
+
+    .toolbar-left, .toolbar-right {
+      display: flex;
+      align-items: center;
+    }
+  }
+
+  .main-content {
+    flex: 1;
+    display: flex;
+    overflow: hidden;
+  }
+
+  .node-panel {
+    width: 220px;
+    background: #fff;
+    border-right: 1px solid #e8e8e8;
+    overflow-y: auto;
+
+    .panel-title {
+      padding: 12px 16px;
+      font-weight: 600;
+      border-bottom: 1px solid #e8e8e8;
+    }
+
+    .node-category {
+      .category-title {
+        padding: 8px 16px;
+        font-size: 12px;
+        color: #999;
+        background: #fafafa;
+      }
+
+      .node-list {
+        padding: 8px;
+      }
+
+      .node-item {
+        display: flex;
+        align-items: center;
+        padding: 8px 12px;
+        margin-bottom: 8px;
+        background: #fff;
+        border: 1px solid #e8e8e8;
+        border-radius: 4px;
+        cursor: grab;
+        transition: all 0.2s;
+
+        &:hover {
+          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+        }
+
+        i {
+          font-size: 18px;
+          margin-right: 8px;
+        }
+
+        span {
+          font-size: 13px;
+        }
+      }
+    }
+  }
+
+  .canvas-container {
+    flex: 1;
+    position: relative;
+    overflow: auto;
+
+    .canvas-svg {
+      min-width: 100%;
+      min-height: 100%;
+      cursor: grab;
+
+      &.dragging-canvas {
+        cursor: grabbing;
+      }
+
+      .node-group {
+        cursor: move;
+
+        rect {
+          transition: stroke-width 0.2s;
+
+          &.selected {
+            stroke-width: 3;
+          }
+        }
+
+        .node-content {
+          display: flex;
+          align-items: center;
+          height: 100%;
+          padding: 0 10px;
+          overflow: hidden;
+
+          i {
+            font-size: 16px;
+            margin-right: 6px;
+            flex-shrink: 0;
+          }
+
+          .node-name {
+            font-size: 12px;
+            color: #333;
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            flex: 1;
+            min-width: 0;
+          }
+        }
+
+        .anchor {
+          opacity: 0;
+          cursor: crosshair;
+          transition: opacity 0.2s;
+        }
+
+        &:hover .anchor {
+          opacity: 1;
+        }
+      }
+
+      .edge-group {
+        cursor: pointer;
+
+        path {
+          transition: stroke-width 0.2s;
+
+          &.selected {
+            stroke-width: 3;
+          }
+        }
+      }
+    }
+  }
+
+  .property-panel {
+    width: 280px;
+    background: #fff;
+    border-left: 1px solid #e8e8e8;
+    overflow-y: auto;
+
+    .panel-title {
+      padding: 12px 16px;
+      font-weight: 600;
+      border-bottom: 1px solid #e8e8e8;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+
+      .el-icon-close {
+        cursor: pointer;
+        color: #999;
+
+        &:hover {
+          color: #333;
+        }
+      }
+    }
+
+    .el-form {
+      padding: 16px;
+    }
+  }
+}

+ 732 - 0
src/views/his/aiWorkflow/design.vue

@@ -0,0 +1,732 @@
+<template>
+  <div class="workflow-designer">
+    <!-- 顶部工具栏 -->
+    <div class="toolbar">
+      <div class="toolbar-left">
+        <el-button icon="el-icon-back" size="small" @click="goBack">返回</el-button>
+        <el-divider direction="vertical" />
+        <el-input v-model="form.workflowName" placeholder="工作流名称" size="small" style="width: 200px" />
+        <el-select v-model="form.workflowType" placeholder="类型" size="small" style="width: 120px; margin-left: 10px">
+          <el-option
+            v-for="dict in workflowTypeOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </div>
+      <div class="toolbar-right">
+        <el-input
+          v-model="form.workflowDesc"
+          placeholder="工作流描述"
+          size="small"
+          style="width: 200px; margin-right: 10px"
+        />
+        <el-divider direction="vertical" />
+        <el-button icon="el-icon-zoom-in" size="small" @click="zoomIn">放大</el-button>
+        <el-button icon="el-icon-zoom-out" size="small" @click="zoomOut">缩小</el-button>
+        <el-button icon="el-icon-rank" size="small" @click="fitView">适应</el-button>
+        <el-divider direction="vertical" />
+        <el-button type="primary" icon="el-icon-check" size="small" @click="handleSave">保存</el-button>
+      </div>
+    </div>
+
+    <div class="main-content">
+      <!-- 左侧节点面板 -->
+      <div class="node-panel">
+        <div class="panel-title">节点类型</div>
+        <div class="node-category" v-for="category in nodeCategories" :key="category.key">
+          <div class="category-title">{{ category.name }}</div>
+          <div class="node-list">
+            <div
+              class="node-item"
+              v-for="nodeType in category.types"
+              :key="nodeType.typeCode"
+              draggable="true"
+              @dragstart="onDragStart($event, nodeType)"
+              :style="{ borderColor: nodeType.typeColor }"
+            >
+              <i :class="nodeType.typeIcon" :style="{ color: nodeType.typeColor }"></i>
+              <span>{{ nodeType.typeName }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 中间画布区域 -->
+      <div
+        class="canvas-container"
+        ref="canvasContainer"
+        tabindex="0"
+        @drop="onDrop"
+        @dragover.prevent
+        @click="onCanvasClick"
+        @mousedown="onCanvasMouseDown"
+        @keydown.delete="handleDelete"
+        @keydown.backspace="handleDelete"
+      >
+        <svg class="canvas-svg" ref="canvasSvg" :width="canvasSize.width" :height="canvasSize.height"
+          :class="{ 'dragging-canvas': isDraggingCanvas }">
+          <!-- 网格背景 -->
+          <defs>
+            <pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
+              <path d="M 20 0 L 0 0 0 20" fill="none" stroke="#e0e0e0" stroke-width="0.5"/>
+            </pattern>
+          </defs>
+          <rect :width="canvasSize.width" :height="canvasSize.height" fill="url(#grid)" />
+
+          <!-- 连线 -->
+          <g class="edges-layer" :transform="`translate(${canvasOffset.x}, ${canvasOffset.y}) scale(${scale})`">
+            <g
+              v-for="edge in edges"
+              :key="edge.edgeKey"
+              class="edge-group"
+              @click.stop="selectEdge(edge)"
+            >
+              <path
+                :d="getEdgePath(edge)"
+                :stroke="edge.edgeColor || '#999'"
+                stroke-width="2"
+                fill="none"
+                :class="{ selected: selectedEdge === edge }"
+              />
+              <text
+                v-if="edge.edgeLabel"
+                :x="getEdgeLabelPos(edge).x"
+                :y="getEdgeLabelPos(edge).y"
+                text-anchor="middle"
+                font-size="12"
+                fill="#666"
+              >{{ edge.edgeLabel }}</text>
+            </g>
+          </g>
+
+          <!-- 临时连线 -->
+          <g :transform="`translate(${canvasOffset.x}, ${canvasOffset.y}) scale(${scale})`">
+            <path
+              v-if="tempEdge"
+              :d="tempEdge.path"
+              stroke="#1890ff"
+              stroke-width="2"
+              fill="none"
+              stroke-dasharray="5,5"
+            />
+          </g>
+
+          <!-- 节点 -->
+          <g class="nodes-layer" :transform="`translate(${canvasOffset.x}, ${canvasOffset.y}) scale(${scale})`">
+            <g
+              v-for="node in nodes"
+              :key="node.nodeKey"
+              class="node-group"
+              :transform="`translate(${node.posX}, ${node.posY})`"
+              @mousedown="onNodeMouseDown($event, node)"
+              @click.stop="selectNode(node)"
+            >
+              <rect
+                :width="nodeWidth"
+                :height="node.height || 36"
+                rx="6"
+                ry="6"
+                :fill="getNodeBgColor(node)"
+                :stroke="node.nodeColor || '#1890ff'"
+                stroke-width="2"
+                :class="{ selected: selectedNode === node }"
+              />
+              <foreignObject :width="nodeWidth" :height="node.height || 36">
+                <div class="node-content" xmlns="http://www.w3.org/1999/xhtml" :title="node.nodeName">
+                  <i :class="node.nodeIcon" :style="{ color: node.nodeColor }"></i>
+                  <span class="node-name">{{ node.nodeName }}</span>
+                </div>
+              </foreignObject>
+              <!-- 连接点 -->
+              <circle :cx="nodeWidth / 2" cy="0" r="5" fill="#fff" stroke="#1890ff" stroke-width="2"
+                class="anchor top" @mousedown.stop="startConnect($event, node, 'top')" />
+              <circle :cx="nodeWidth / 2" :cy="node.height || 36" r="5" fill="#fff" stroke="#1890ff" stroke-width="2"
+                class="anchor bottom" @mousedown.stop="startConnect($event, node, 'bottom')" />
+              <circle cx="0" :cy="(node.height || 36) / 2" r="5" fill="#fff" stroke="#1890ff" stroke-width="2"
+                class="anchor left" @mousedown.stop="startConnect($event, node, 'left')" />
+              <circle :cx="nodeWidth" :cy="(node.height || 36) / 2" r="5" fill="#fff" stroke="#1890ff" stroke-width="2"
+                class="anchor right" @mousedown.stop="startConnect($event, node, 'right')" />
+            </g>
+          </g>
+        </svg>
+      </div>
+
+      <!-- 右侧属性面板 -->
+      <div class="property-panel" v-if="selectedNode || selectedEdge">
+        <div class="panel-title">
+          {{ selectedNode ? '节点属性' : '连线属性' }}
+          <i class="el-icon-close" @click="clearSelection"></i>
+        </div>
+
+        <!-- 节点属性 -->
+        <el-form v-if="selectedNode" label-width="80px" size="small">
+          <el-form-item label="节点内容">
+            <el-input
+              v-model="selectedNode.nodeName"
+              type="textarea"
+              :autosize="{ minRows: 1, maxRows: 6 }"
+            />
+          </el-form-item>
+          <div v-if="selectedNode.nodeType == 'http'">
+            <el-form-item label="URL地址">
+              <el-input v-model="selectedNode.nodeConfig.url" />
+            </el-form-item>
+          </div>
+          <el-form-item label="节点类型">
+            <el-input :value="getNodeTypeName(selectedNode.nodeType)" disabled />
+          </el-form-item>
+          <el-form-item label="节点颜色">
+            <el-color-picker v-model="selectedNode.nodeColor" />
+          </el-form-item>
+          <el-form-item label="X坐标">
+            <el-input-number v-model="selectedNode.posX" :min="0" />
+          </el-form-item>
+          <el-form-item label="Y坐标">
+            <el-input-number v-model="selectedNode.posY" :min="0" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="danger" size="mini" @click="deleteNode">删除节点</el-button>
+          </el-form-item>
+        </el-form>
+
+        <!-- 连线属性 -->
+        <el-form v-if="selectedEdge" label-width="80px" size="small">
+          <el-form-item label="连线标签">
+            <el-input v-model="selectedEdge.edgeLabel" />
+          </el-form-item>
+          <el-form-item label="连线颜色">
+            <el-color-picker v-model="selectedEdge.edgeColor" />
+          </el-form-item>
+          <el-form-item label="条件表达式">
+            <el-input v-model="selectedEdge.conditionExpr" type="textarea" :rows="3" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="danger" size="mini" @click="deleteEdge">删除连线</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getWorkflow, addWorkflow, updateWorkflow, getNodeTypes } from '@/api/his/aiWorkflow'
+
+export default {
+  name: 'WorkflowDesign',
+  data() {
+    return {
+      // 工作流ID
+      workflowId: null,
+      // 表单数据
+      form: {
+        workflowName: '新建工作流',
+        workflowDesc: '',
+        workflowType: '1',
+        canvasData: ''
+      },
+      // 节点列表
+      nodes: [],
+      // 连线列表
+      edges: [],
+      // 节点类型列表
+      nodeTypes: [],
+      // 节点分类
+      nodeCategories: [],
+      // 选中的节点
+      selectedNode: null,
+      // 选中的连线
+      selectedEdge: null,
+      // 缩放比例
+      scale: 1,
+      // 画布偏移
+      canvasOffset: { x: 0, y: 0 },
+      // 拖拽中的节点
+      draggingNode: null,
+      // 拖拽偏移
+      dragOffset: { x: 0, y: 0 },
+      // 是否正在连线
+      connecting: false,
+      // 连线起点
+      connectStart: null,
+      // 临时连线
+      tempEdge: null,
+      // 画布尺寸
+      canvasSize: { width: 2000, height: 2000 },
+      // 节点固定宽度
+      nodeWidth: 120,
+      // 是否正在拖动画布
+      isDraggingCanvas: false,
+      // 画布拖动起始点
+      canvasDragStart: { x: 0, y: 0 },
+      // 工作流类型字典
+      workflowTypeOptions: [
+        { dictValue: '1', dictLabel: '对话流程' },
+        { dictValue: '2', dictLabel: '任务流程' },
+        { dictValue: '3', dictLabel: '审批流程' }
+      ]
+    }
+  },
+  created() {
+    this.workflowId = this.$route.params.id
+    this.loadNodeTypes()
+    if (this.workflowId) {
+      this.loadWorkflow()
+    }
+  },
+  mounted() {
+    // 确保容器可获取焦点
+    this.$nextTick(() => {
+      this.$refs.canvasContainer.focus()  // 这里应该是 canvasContainer 不是 container
+    })
+
+    // 添加全局键盘事件监听
+    // window.addEventListener('keydown', this.handleGlobalKeydown)
+    document.addEventListener('mousemove', this.onMouseMove)
+    document.addEventListener('mouseup', this.onMouseUp)
+  },
+  beforeDestroy() {
+    // window.removeEventListener('keydown', this.handleGlobalKeydown)
+    document.removeEventListener('mousemove', this.onMouseMove)
+    document.removeEventListener('mouseup', this.onMouseUp)
+  },
+  methods: {
+    // 聚焦画布容器
+    focusCanvasContainer() {
+      this.$refs.canvasContainer.focus()
+    },
+
+    // 点击画布时聚焦
+    onCanvasClick() {
+      this.clearSelection()
+      this.focusCanvasContainer()
+    },
+
+    // 点击节点时聚焦
+    selectNode(node) {
+      this.selectedNode = node
+      this.selectedNode.nodeConfig = JSON.parse(node.nodeConfig)
+      this.selectedEdge = null
+      this.focusCanvasContainer()
+    },
+
+    // 点击连线时聚焦
+    selectEdge(edge) {
+      this.selectedEdge = edge
+      this.selectedNode = null
+      this.focusCanvasContainer()
+    },
+
+    // 局部键盘事件
+    handleDelete(event) {
+      event.preventDefault() // 阻止默认行为(如浏览器后退)
+      this.deleteSelected()
+    },
+
+    // 全局键盘事件
+    // handleGlobalKeydown(event) {
+    //   // 检查是否按下了退格键或删除键
+    //   if ((event.key === 'Backspace' || event.key === 'Delete') && !this.isInputFocused()) {
+    //     event.preventDefault()
+    //     this.deleteSelected()
+    //   }
+    // },
+
+    // 删除选中项的逻辑
+    deleteSelected() {
+      if (this.selectedNode) {
+        this.deleteSelectedNode()
+      } else if (this.selectedEdge) {
+        this.deleteSelectedEdge()
+      }
+    },
+
+    // 删除选中节点
+    deleteSelectedNode() {
+      if (!this.selectedNode) return
+      // 确认对话框
+      this.$confirm('是否确认删除该节点?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        const key = this.selectedNode.nodeKey
+        this.nodes = this.nodes.filter(n => n.nodeKey !== key)
+        // 同时删除与该节点相关的连线
+        this.edges = this.edges.filter(e =>
+          e.sourceNodeKey !== key && e.targetNodeKey !== key)
+        this.selectedNode = null
+      }).catch(() => {})
+    },
+
+    // 删除选中连线
+    deleteSelectedEdge() {
+      if (!this.selectedEdge) return
+
+      this.$confirm('是否确认删除该连线?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.edges = this.edges.filter(e => e.edgeKey !== this.selectedEdge.edgeKey)
+        this.selectedEdge = null
+      }).catch(() => {})
+    },
+
+    // 检查是否有输入框获得焦点
+    isInputFocused() {
+      const activeElement = document.activeElement
+      // 注意:节点名称输入框也需要排除
+      const isEditable = ['INPUT', 'TEXTAREA', 'SELECT'].includes(activeElement.tagName)
+      // 额外检查是否是节点名称输入框
+      const isNodeInput = activeElement.classList && activeElement.classList.contains('node-name-input')
+      return isEditable || isNodeInput
+    },
+
+    /** 加载节点类型 */
+    loadNodeTypes() {
+      getNodeTypes().then(res => {
+        this.nodeTypes = res.data || []
+        this.groupNodeTypes()
+      }).catch(() => {
+        // 如果接口未实现,使用默认节点类型
+        this.nodeTypes = [
+          { typeCode: 'start', typeName: '开始', typeCategory: 'basic', typeIcon: 'el-icon-video-play', typeColor: '#52c41a' },
+          { typeCode: 'end', typeName: '结束', typeCategory: 'basic', typeIcon: 'el-icon-video-pause', typeColor: '#ff4d4f' },
+          { typeCode: 'condition', typeName: '条件判断', typeCategory: 'logic', typeIcon: 'el-icon-question', typeColor: '#faad14' },
+          { typeCode: 'parallel', typeName: '并行网关', typeCategory: 'logic', typeIcon: 'el-icon-share', typeColor: '#722ed1' },
+          { typeCode: 'ai_chat', typeName: 'AI对话', typeCategory: 'ai', typeIcon: 'el-icon-chat-dot-round', typeColor: '#1890ff' },
+          { typeCode: 'ai_analysis', typeName: 'AI分析', typeCategory: 'ai', typeIcon: 'el-icon-data-analysis', typeColor: '#13c2c2' },
+          { typeCode: 'http', typeName: 'HTTP请求', typeCategory: 'integration', typeIcon: 'el-icon-link', typeColor: '#eb2f96' },
+          { typeCode: 'database', typeName: '数据库', typeCategory: 'integration', typeIcon: 'el-icon-coin', typeColor: '#fa8c16' }
+        ]
+        this.groupNodeTypes()
+      })
+    },
+    /** 节点类型分组 */
+    groupNodeTypes() {
+      const categoryMap = {
+        basic: { key: 'basic', name: '基础节点', types: [] },
+        logic: { key: 'logic', name: '逻辑节点', types: [] },
+        ai: { key: 'ai', name: 'AI节点', types: [] },
+        ai: { key: 'ai-cell', name: '外呼几点', types: [] },
+        integration: { key: 'integration', name: '集成节点', types: [] }
+      }
+      this.nodeTypes.forEach(t => {
+        const cat = categoryMap[t.typeCategory] || categoryMap.basic
+        cat.types.push(t)
+      })
+      this.nodeCategories = Object.values(categoryMap).filter(c => c.types.length > 0)
+    },
+    /** 加载工作流 */
+    loadWorkflow() {
+      getWorkflow(this.workflowId).then(res => {
+        const data = res.data
+        this.form = {
+          workflowName: data.workflowName,
+          workflowDesc: data.workflowDesc,
+          workflowType: String(data.workflowType),
+          canvasData: data.canvasData
+        }
+        this.nodes = data.nodes || []
+        this.edges = data.edges || []
+      })
+    },
+    /** 返回列表 */
+    goBack() {
+      this.$router.push('/his/aiWorkflow')
+    },
+    /** 放大 */
+    zoomIn() {
+      this.scale = Math.min(2, this.scale + 0.1)
+    },
+    /** 缩小 */
+    zoomOut() {
+      this.scale = Math.max(0.5, this.scale - 0.1)
+    },
+    /** 适应画布 */
+    fitView() {
+      this.scale = 1
+      this.canvasOffset = { x: 0, y: 0 }
+      this.focusCanvasContainer()
+    },
+    /** 生成唯一Key */
+    generateKey() {
+      return 'node_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
+    },
+    /** 拖拽开始 */
+    onDragStart(e, nodeType) {
+      e.dataTransfer.setData('nodeType', JSON.stringify(nodeType))
+    },
+    /** 放置节点 */
+    onDrop(e) {
+      const nodeTypeStr = e.dataTransfer.getData('nodeType')
+      if (!nodeTypeStr) return
+      const nodeType = JSON.parse(nodeTypeStr)
+      const rect = this.$refs.canvasContainer.getBoundingClientRect()
+      const x = (e.clientX - rect.left - this.canvasOffset.x) / this.scale
+      const y = (e.clientY - rect.top - this.canvasOffset.y) / this.scale
+
+      const newNode = {
+        nodeKey: this.generateKey(),
+        nodeName: nodeType.typeName,
+        nodeType: nodeType.typeCode,
+        nodeIcon: nodeType.typeIcon,
+        nodeColor: nodeType.typeColor,
+        posX: Math.round(x - 50),
+        posY: Math.round(y - 20),
+        height: 40,
+        nodeConfig: nodeType.defaultConfig || '{}'
+      }
+      this.nodes.push(newNode)
+      this.selectNode(newNode)
+      this.focusCanvasContainer()
+      // 检测并扩展画布
+      this.checkAndExpandCanvas(newNode)
+    },
+
+    /** 画布鼠标按下 */
+    onCanvasMouseDown(e) {
+      // 只响应左键且点击在空白区域
+      if (e.button !== 0) return
+      if (e.target.tagName === 'rect' && e.target.getAttribute('fill') === 'url(#grid)') {
+        this.isDraggingCanvas = true
+        this.canvasDragStart = {
+          x: e.clientX,
+          y: e.clientY,
+          scrollLeft: this.$refs.canvasContainer.scrollLeft,
+          scrollTop: this.$refs.canvasContainer.scrollTop
+        }
+        e.preventDefault()
+      }
+    },
+
+    /** 清除选中 */
+    clearSelection() {
+      this.selectedNode = null
+      this.selectedEdge = null
+    },
+    /** 节点鼠标按下 */
+    onNodeMouseDown(e, node) {
+      if (e.target.classList.contains('anchor')) return
+      this.draggingNode = node
+      const rect = this.$refs.canvasContainer.getBoundingClientRect()
+      const scrollLeft = this.$refs.canvasContainer.scrollLeft
+      const scrollTop = this.$refs.canvasContainer.scrollTop
+      this.dragOffset = {
+        x: (e.clientX - rect.left + scrollLeft - this.canvasOffset.x) / this.scale - node.posX,
+        y: (e.clientY - rect.top + scrollTop - this.canvasOffset.y) / this.scale - node.posY
+      }
+      this.selectNode(node)
+    },
+    /** 鼠标移动 */
+    onMouseMove(e) {
+      // 拖动画布
+      if (this.isDraggingCanvas) {
+        const dx = e.clientX - this.canvasDragStart.x
+        const dy = e.clientY - this.canvasDragStart.y
+        this.$refs.canvasContainer.scrollLeft = this.canvasDragStart.scrollLeft - dx
+        this.$refs.canvasContainer.scrollTop = this.canvasDragStart.scrollTop - dy
+        return
+      }
+      // 拖动节点
+      if (this.draggingNode) {
+        const rect = this.$refs.canvasContainer.getBoundingClientRect()
+        const scrollLeft = this.$refs.canvasContainer.scrollLeft
+        const scrollTop = this.$refs.canvasContainer.scrollTop
+        const x = (e.clientX - rect.left + scrollLeft - this.canvasOffset.x) / this.scale - this.dragOffset.x
+        const y = (e.clientY - rect.top + scrollTop - this.canvasOffset.y) / this.scale - this.dragOffset.y
+        this.draggingNode.posX = Math.max(0, Math.round(x))
+        this.draggingNode.posY = Math.max(0, Math.round(y))
+        // 检测并扩展画布
+        this.checkAndExpandCanvas(this.draggingNode)
+      }
+      // 连线
+      if (this.connecting && this.connectStart) {
+        const rect = this.$refs.canvasContainer.getBoundingClientRect()
+        const scrollLeft = this.$refs.canvasContainer.scrollLeft
+        const scrollTop = this.$refs.canvasContainer.scrollTop
+        const endX = (e.clientX - rect.left + scrollLeft - this.canvasOffset.x) / this.scale
+        const endY = (e.clientY - rect.top + scrollTop - this.canvasOffset.y) / this.scale
+        this.tempEdge = {
+          path: this.calcPath(this.connectStart.x, this.connectStart.y, endX, endY)
+        }
+      }
+    },
+    /** 检测并扩展画布 */
+    checkAndExpandCanvas(node) {
+      const nodeHeight = node.height || 36
+      const padding = 200 // 边缘预留空间
+      const expandStep = 500 // 每次扩展的大小
+
+      const nodeRight = node.posX + this.nodeWidth
+      const nodeBottom = node.posY + nodeHeight
+
+      // 检测右边缘
+      if (nodeRight + padding > this.canvasSize.width) {
+        this.canvasSize.width += expandStep
+      }
+      // 检测下边缘
+      if (nodeBottom + padding > this.canvasSize.height) {
+        this.canvasSize.height += expandStep
+      }
+    },
+    /** 鼠标松开 */
+    onMouseUp(e) {
+      // 停止拖动画布
+      if (this.isDraggingCanvas) {
+        this.isDraggingCanvas = false
+        this.focusCanvasContainer()
+        return
+      }
+      // 停止连线
+      if (this.connecting) {
+        const targetNode = this.findNodeAtPoint(e.clientX, e.clientY)
+        if (targetNode && targetNode.nodeKey !== this.connectStart.nodeKey) {
+          this.createEdge(this.connectStart.nodeKey, targetNode.nodeKey,
+            this.connectStart.anchor, 'top')
+        }
+        this.connecting = false
+        this.connectStart = null
+        this.tempEdge = null
+      }
+      this.draggingNode = null
+    },
+    /** 开始连线 */
+    startConnect(e, node, anchor) {
+      this.connecting = true
+      const anchorPos = this.getAnchorPos(node, anchor)
+      this.connectStart = {
+        nodeKey: node.nodeKey,
+        anchor: anchor,
+        x: anchorPos.x,
+        y: anchorPos.y
+      }
+      this.selectNode(node)
+    },
+    /** 获取锚点位置 */
+    getAnchorPos(node, anchor) {
+      const w = this.nodeWidth
+      const h = node.height || 36
+      switch (anchor) {
+        case 'top': return { x: node.posX + w / 2, y: node.posY }
+        case 'bottom': return { x: node.posX + w / 2, y: node.posY + h }
+        case 'left': return { x: node.posX, y: node.posY + h / 2 }
+        case 'right': return { x: node.posX + w, y: node.posY + h / 2 }
+        default: return { x: node.posX + w / 2, y: node.posY + h }
+      }
+    },
+    /** 查找点击位置的节点 */
+    findNodeAtPoint(clientX, clientY) {
+      const rect = this.$refs.canvasContainer.getBoundingClientRect()
+      const scrollLeft = this.$refs.canvasContainer.scrollLeft
+      const scrollTop = this.$refs.canvasContainer.scrollTop
+      const x = (clientX - rect.left + scrollLeft - this.canvasOffset.x) / this.scale
+      const y = (clientY - rect.top + scrollTop - this.canvasOffset.y) / this.scale
+      return this.nodes.find(n => {
+        const w = this.nodeWidth
+        const h = n.height || 36
+        return x >= n.posX && x <= n.posX + w && y >= n.posY && y <= n.posY + h
+      })
+    },
+    /** 创建连线 */
+    createEdge(sourceKey, targetKey, sourceAnchor, targetAnchor) {
+      const exists = this.edges.find(e =>
+        e.sourceNodeKey === sourceKey && e.targetNodeKey === targetKey)
+      if (exists) return
+
+      this.edges.push({
+        edgeKey: 'edge_' + Date.now(),
+        sourceNodeKey: sourceKey,
+        targetNodeKey: targetKey,
+        sourceAnchor: sourceAnchor,
+        targetAnchor: targetAnchor,
+        edgeType: 'smoothstep',
+        edgeColor: '#999999'
+      })
+    },
+    /** 获取连线路径 */
+    getEdgePath(edge) {
+      const sourceNode = this.nodes.find(n => n.nodeKey === edge.sourceNodeKey)
+      const targetNode = this.nodes.find(n => n.nodeKey === edge.targetNodeKey)
+      if (!sourceNode || !targetNode) return ''
+
+      const start = this.getAnchorPos(sourceNode, edge.sourceAnchor || 'bottom')
+      const end = this.getAnchorPos(targetNode, edge.targetAnchor || 'top')
+      return this.calcPath(start.x, start.y, end.x, end.y)
+    },
+    /** 计算贝塞尔曲线路径 */
+    calcPath(x1, y1, x2, y2) {
+      const midY = (y1 + y2) / 2
+      return `M ${x1} ${y1} C ${x1} ${midY}, ${x2} ${midY}, ${x2} ${y2}`
+    },
+    /** 获取连线标签位置 */
+    getEdgeLabelPos(edge) {
+      const sourceNode = this.nodes.find(n => n.nodeKey === edge.sourceNodeKey)
+      const targetNode = this.nodes.find(n => n.nodeKey === edge.targetNodeKey)
+      if (!sourceNode || !targetNode) return { x: 0, y: 0 }
+      const start = this.getAnchorPos(sourceNode, edge.sourceAnchor || 'bottom')
+      const end = this.getAnchorPos(targetNode, edge.targetAnchor || 'top')
+      return { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 }
+    },
+    /** 获取节点背景色 */
+    getNodeBgColor(node) {
+      const color = node.nodeColor || '#1890ff'
+      return color + '15'
+    },
+
+    /** 获取节点类型名称 */
+    getNodeTypeName(typeCode) {
+      const t = this.nodeTypes.find(n => n.typeCode === typeCode)
+      return t ? t.typeName : typeCode
+    },
+    /** 删除节点(按钮触发) */
+    deleteNode() {
+      this.deleteSelectedNode()
+    },
+    /** 删除连线(按钮触发) */
+    deleteEdge() {
+      this.deleteSelectedEdge()
+    },
+    /** 保存工作流 */
+    handleSave() {
+      if (!this.form.workflowName) {
+        this.msgWarning('请输入工作流名称')
+        return
+      }
+      const nodes = JSON.parse(JSON.stringify(this.nodes))
+      nodes.filter(node => typeof node.nodeConfig == 'object').forEach(node => {
+        console.log(typeof node.nodeConfig)
+        node.nodeConfig = JSON.stringify(node.nodeConfig);
+      })
+      const data = {
+        workflowId: this.workflowId,
+        workflowName: this.form.workflowName,
+        workflowDesc: this.form.workflowDesc,
+        workflowType: this.form.workflowType,
+        canvasData: JSON.stringify({ scale: this.scale, offset: this.canvasOffset }),
+        nodes: nodes,
+        edges: this.edges
+      }
+
+      if (this.workflowId) {
+        updateWorkflow(data).then(() => {
+          this.msgSuccess('保存成功')
+        })
+      } else {
+        addWorkflow(data).then(res => {
+          this.msgSuccess('保存成功')
+          this.workflowId = res.data
+          this.$router.replace('/his/aiWorkflow/design/' + this.workflowId)
+        })
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import './design.scss';
+</style>

+ 499 - 0
src/views/his/aiWorkflow/index.vue

@@ -0,0 +1,499 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
+      <el-form-item label="工作流名称" prop="workflowName">
+        <el-input
+          v-model="queryParams.workflowName"
+          placeholder="请输入工作流名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="工作流类型" prop="workflowType">
+        <el-select v-model="queryParams.workflowType" placeholder="请选择类型" clearable size="small">
+          <el-option
+            v-for="dict in workflowTypeOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </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:aiWorkflow:add']"
+        >新增</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:aiWorkflow: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="['his:aiWorkflow:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" border :data="workflowList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="workflowId" width="80" />
+      <el-table-column label="工作流名称" align="center" prop="workflowName" min-width="150" show-overflow-tooltip />
+      <el-table-column label="描述" align="center" prop="workflowDesc" min-width="200" show-overflow-tooltip />
+      <el-table-column label="类型" align="center" prop="workflowType" width="100">
+        <template slot-scope="scope">
+          <dict-tag :options="workflowTypeOptions" :value="scope.row.workflowType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status" width="80">
+        <template slot-scope="scope">
+          <el-switch
+            v-model="scope.row.status"
+            :active-value="1"
+            :inactive-value="0"
+            @change="handleStatusChange(scope.row)"
+          />
+        </template>
+      </el-table-column>
+      <el-table-column label="版本" align="center" prop="version" width="60" />
+      <el-table-column label="创建时间" align="center" prop="createTime" width="160">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleEdit(scope.row)"
+            v-hasPermi="['his:aiWorkflow:edit']"
+          >设计</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-user"
+            @click="handleBindSales(scope.row)"
+          >绑定销售</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-document-copy"
+            @click="handleCopy(scope.row)"
+            v-hasPermi="['his:aiWorkflow:add']"
+          >复制</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['his:aiWorkflow: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="绑定销售" :visible.sync="bindSalesOpen" width="500px" append-to-body>
+      <!-- 当前绑定的销售 -->
+      <div class="current-sales">
+        <div class="section-title">当前绑定销售</div>
+        <div v-if="currentBindUserList && currentBindUserList.length > 0" class="bind-info">
+          <el-tag 
+            v-for="(user, index) in currentBindUserList" 
+            :key="index" 
+            type="success"
+            style="margin-right: 8px; margin-bottom: 8px;"
+          >
+            {{ user.userName || user.user_name }}({{ user.nickName || user.nick_name }})
+          </el-tag>
+        </div>
+        <div v-else class="no-bind">
+          <el-tag type="info">暂未绑定销售</el-tag>
+        </div>
+      </div>
+
+      <!-- 可选销售列表 -->
+      <div class="sales-list">
+        <div class="section-title">选择销售</div>
+        <el-input
+          v-model="salesSearchKey"
+          placeholder="搜索昵称"
+          size="small"
+          prefix-icon="el-icon-search"
+          clearable
+          style="margin-bottom: 10px"
+        />
+        <el-table
+          :data="filteredCompanyUserList"
+          v-loading="salesLoading"
+          border
+          height="300"
+          :row-key="getSalesRowKey"
+          :row-class-name="salesRowClassName"
+          @selection-change="handleSalesSelectionChange"
+          ref="salesTable"
+        >
+          <el-table-column type="selection" width="55" align="center" :selectable="checkSelectable" :reserve-selection="true" />
+          <el-table-column label="用户名" align="center" prop="userName" />
+          <el-table-column label="昵称" align="center" prop="nickName" />
+        </el-table>
+      </div>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="handleBindSalesCancel">取 消</el-button>
+        <el-button type="primary" :disabled="selectedSalesList.length === 0" @click="confirmBindSales">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listWorkflow, delWorkflow, updateWorkflowStatus, copyWorkflow, exportWorkflow, getBindCompanyUserByWorkflowId, listCompanyUser, updateWorkflowBindCompanyUser } from '@/api/his/aiWorkflow'
+
+export default {
+  name: 'AiWorkflow',
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 工作流表格数据
+      workflowList: [],
+      // 工作流类型字典
+      workflowTypeOptions: [
+        { dictValue: '1', dictLabel: '对话流程' },
+        { dictValue: '2', dictLabel: '任务流程' },
+        { dictValue: '3', dictLabel: '审批流程' }
+      ],
+      // 状态字典
+      statusOptions: [
+        { dictValue: '1', dictLabel: '启用' },
+        { dictValue: '0', dictLabel: '禁用' }
+      ],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        workflowName: null,
+        workflowType: null,
+        status: null
+      },
+      // 绑定销售弹窗
+      bindSalesOpen: false,
+      // 销售列表加载
+      salesLoading: false,
+      // 当前操作的工作流
+      currentWorkflow: null,
+      // 当前绑定的用户列表
+      currentBindUserList: [],
+      // 销售列表
+      companyUserList: [],
+      // 选中的销售列表
+      selectedSalesList: [],
+      // 销售搜索关键字
+      salesSearchKey: ''
+    }
+  },
+  computed: {
+    // 过滤后的销售列表
+    filteredCompanyUserList() {
+      if (!this.salesSearchKey) {
+        return this.companyUserList
+      }
+      const key = this.salesSearchKey.toLowerCase()
+      return this.companyUserList.filter(item => {
+        return (item.nickName && item.nickName.toLowerCase().includes(key))
+      })
+    }
+  },
+  created() {
+    this.getList()
+    // 如果有字典配置,可以使用以下方式获取
+    // this.getDicts("ai_workflow_type").then(response => {
+    //   this.workflowTypeOptions = response.data
+    // })
+  },
+  methods: {
+    /** 查询工作流列表 */
+    getList() {
+      this.loading = true
+      listWorkflow(this.queryParams).then(response => {
+        this.workflowList = 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.workflowId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.$router.push('/his/aiWorkflow/design')
+    },
+    /** 设计按钮操作 */
+    handleEdit(row) {
+      this.$router.push('/his/aiWorkflow/design/' + row.workflowId)
+    },
+    /** 复制按钮操作 */
+    handleCopy(row) {
+      this.$confirm('是否确认复制该工作流?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return copyWorkflow(row.workflowId)
+      }).then(() => {
+        this.getList()
+        this.msgSuccess('复制成功')
+      }).catch(() => {})
+    },
+    /** 状态修改 */
+    handleStatusChange(row) {
+      const text = row.status === 1 ? '启用' : '停用'
+      this.$confirm('确认要"' + text + '"该工作流吗?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return updateWorkflowStatus(row.workflowId, row.status)
+      }).then(() => {
+        this.msgSuccess(text + '成功')
+      }).catch(() => {
+        row.status = row.status === 1 ? 0 : 1
+      })
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const workflowIds = row.workflowId || this.ids
+      this.$confirm('是否确认删除工作流编号为"' + workflowIds + '"的数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return delWorkflow(workflowIds)
+      }).then(() => {
+        this.getList()
+        this.msgSuccess('删除成功')
+      }).catch(() => {})
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams
+      this.$confirm('是否确认导出所有工作流数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.exportLoading = true
+        return exportWorkflow(queryParams)
+      }).then(response => {
+        this.download(response.msg)
+        this.exportLoading = false
+      }).catch(() => {})
+    },
+    /** 绑定销售按钮操作 */
+    handleBindSales(row) {
+      this.currentWorkflow = row
+      this.currentBindUserList = []
+      this.selectedSalesList = []
+      this.salesSearchKey = ''
+      this.bindSalesOpen = true
+      this.salesLoading = true
+      console.log(11111)
+      // 获取当前绑定的销售列表
+      getBindCompanyUserByWorkflowId(row.workflowId).then(res => {
+        this.currentBindUserList = res.data.data || []
+      }).catch(() => {})
+      console.log(this.currentBindUserList)
+      // 获取销售列表
+      listCompanyUser().then(res => {
+        this.companyUserList = res.data || res.rows || []
+        this.salesLoading = false
+      }).catch(() => {
+        this.salesLoading = false
+      })
+    },
+    /** 销售勾选变化 */
+    handleSalesSelectionChange(selection) {
+      this.selectedSalesList = selection
+    },
+    /** 获取销售行key */
+    getSalesRowKey(row) {
+      return row.userId || row.companyUserId || row.id
+    },
+    /** 取消绑定销售弹窗 */
+    handleBindSalesCancel() {
+      this.bindSalesOpen = false
+      this.selectedSalesList = []
+      this.$refs.salesTable && this.$refs.salesTable.clearSelection()
+    },
+    /** 销售行样式 */
+    salesRowClassName({ row }) {
+      if (this.isCurrentBindUser(row)) {
+        return 'disabled-row'
+      }
+      return ''
+    },
+    /** 判断是否为当前绑定用户 */
+    isCurrentBindUser(row) {
+      if (!this.currentBindUserList || this.currentBindUserList.length === 0) {
+        return false
+      }
+      const rowUserId = row.userId || row.companyUserId || row.user_id
+      return this.currentBindUserList.some(user => {
+        const bindUserId = user.userId || user.companyUserId || user.user_id
+        return bindUserId == rowUserId
+      })
+    },
+    /** 检查是否可选 */
+    checkSelectable(row) {
+      return !this.isCurrentBindUser(row)
+    },
+    /** 确认绑定销售 */
+    confirmBindSales() {
+      if (this.selectedSalesList.length === 0) {
+        this.msgWarning('请选择要绑定的销售')
+        return
+      }
+
+      const workflowId = this.currentWorkflow.workflowId
+      const companyUserIds = this.selectedSalesList.map(item => item.userId || item.companyUserId)
+      
+      this.doBindSales(workflowId, companyUserIds)
+    },
+    /** 执行绑定销售 */
+    doBindSales(workflowId, companyUserIds) {
+      updateWorkflowBindCompanyUser({
+        workflowId: workflowId,
+        companyUserIds: companyUserIds
+      }).then(() => {
+        this.msgSuccess('绑定成功')
+        this.bindSalesOpen = false
+        this.getList()
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.current-sales {
+  margin-bottom: 20px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid #eee;
+
+  .section-title {
+    font-weight: 600;
+    margin-bottom: 10px;
+    color: #333;
+  }
+
+  .bind-info {
+    .el-tag {
+      font-size: 14px;
+      padding: 8px 15px;
+      height: auto;
+      line-height: 1.5;
+      white-space: normal;
+    }
+  }
+
+  .no-bind {
+    .el-tag {
+      font-size: 14px;
+      padding: 8px 15px;
+      height: auto;
+    }
+  }
+}
+
+.sales-list {
+  .section-title {
+    font-weight: 600;
+    margin-bottom: 10px;
+    color: #333;
+  }
+}
+</style>
+
+<style lang="scss">
+.el-table .disabled-row {
+  background-color: #f5f5f5;
+  color: #999;
+  
+  &:hover > td {
+    background-color: #f5f5f5 !important;
+  }
+}
+</style>

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

@@ -108,7 +108,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table height="660" v-loading="loading" border :data="companyList" @selection-change="handleSelectionChange">
+    <el-table v-loading="loading" border :data="companyList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center"/>
       <el-table-column label="ID" align="center" prop="companyId"/>
       <el-table-column label="企业名" align="center" prop="companyName"/>
@@ -197,7 +197,6 @@
           >扣款
           </el-button>
           <el-button
-            v-if="showRedPacket"
             size="mini"
             type="text"
             icon="el-icon-edit"
@@ -206,7 +205,6 @@
           >红包充值
           </el-button>
           <el-button
-            v-if="showRedPacket"
             size="mini"
             type="text"
             icon="el-icon-edit"

+ 12 - 1
src/views/his/companyRecharge/index.vue

@@ -112,8 +112,19 @@
       </el-table-column>
       <el-table-column label="余额" align="center" prop="balance" />
       <el-table-column label="提交人" align="center" prop="createUserNickName" />
-
       <el-table-column label="审核人" align="center" prop="auditUserNickName" />
+      <el-table-column label="凭证照片" align="center" prop="imgs" >
+        <template slot-scope="scope">
+          <div v-if="scope.row.imgs != null && scope.row.imgs != undefined && scope.row.imgs != ''">
+            <el-image
+              style="width: 80px; height: 80px"
+              :src="scope.row.imgs.split(',')[0]"
+              :preview-src-list="scope.row.imgs.split(',')">
+            </el-image>
+            <p style="margin: 0">({{scope.row.imgs.split(',').length}} 张)</p>
+          </div>
+        </template>
+      </el-table-column>
       <el-table-column label="备注" align="center" prop="remark" />
       <el-table-column label="支付时间" align="center" prop="payTime" width="180"/>
       <el-table-column label="审核时间" align="center" prop="auditTime" width="180"/>

+ 3 - 3
src/views/his/express/index.vue

@@ -380,13 +380,13 @@ export default {
   margin: 0 10px;
 }
 
-/deep/.el-transfer-panel {
+.el-transfer-panel {
     width:40%;
 }
-/deep/.el-transfer-panel__body{
+.el-transfer-panel__body{
   height: 450px;
 }
-/deep/.el-transfer-panel__list.is-filterable {
+.el-transfer-panel__list.is-filterable {
    height: 88%;
 }
 

+ 424 - 79
src/views/his/integralOrder/index.vue

@@ -157,7 +157,7 @@
           v-hasPermi="['his:integralOrder:export']"
         >导出</el-button>
       </el-col>
-      
+
       <el-col :span="1.5">
           <el-button
             type="info"
@@ -220,17 +220,12 @@
       <el-table-column label="ERP账号" align="center" prop="loginAccount" />
       <el-table-column label="ERP电话" align="center" prop="erpPhone" />
       <el-table-column label="订单编号" align="center" prop="orderCode" />
-      <el-table-column label="商品名称" align="center" width="200">
+      <el-table-column label="商品信息" align="center" width="200">
         <template slot-scope="scope">
           <div style="display: flex; align-items: center; justify-content: center;">
-            <span>{{ scope.row.goodsName }}</span>
-            <el-image
-              v-if="scope.row.goodsImage"
-              :src="scope.row.goodsImage"
-              style="width: 40px; height: 40px; margin-left: 8px;"
-              fit="cover"
-              :preview-src-list="[scope.row.goodsImage]"
-            />
+            <span style="white-space: pre-line; text-align: left;">{{ scope.row.goodsName }}</span>
+            <!-- 这里移除了num显示,因为现在goodsName已经包含数量信息 -->
+            <!-- 如果你还需要显示其他内容,可以放在这里 -->
           </div>
         </template>
       </el-table-column>
@@ -378,9 +373,9 @@
       <div v-loading="erpAccountDialog.loading">
         <el-form :model="erpAccountForm" label-width="100px">
           <el-form-item label="ERP账户" required>
-            <el-select 
-              v-model="erpAccountForm.selectedAccount" 
-              placeholder="请选择ERP账户" 
+            <el-select
+              v-model="erpAccountForm.selectedAccount"
+              placeholder="请选择ERP账户"
               style="width: 100%"
               filterable
             >
@@ -393,9 +388,9 @@
             </el-select>
           </el-form-item>
           <el-form-item label="推送手机号">
-            <el-select 
-              v-model="erpAccountForm.selectedPhones" 
-              multiple 
+            <el-select
+              v-model="erpAccountForm.selectedPhones"
+              multiple
               placeholder="请选择推送手机号,不填默认订单用户电话"
               style="width: 100%"
               filterable
@@ -409,7 +404,7 @@
             </el-select>
           </el-form-item>
         </el-form>
-        
+
         <!-- 订单统计信息 -->
         <div class="order-summary" v-if="orderSummary">
           <el-divider content-position="left">订单统计</el-divider>
@@ -435,11 +430,11 @@
           </el-row>
         </div>
       </div>
-      
+
       <div slot="footer" class="dialog-footer">
         <el-button @click="cancelErpAccountDialog">取 消</el-button>
-        <el-button 
-          type="primary" 
+        <el-button
+          type="primary"
           @click="confirmCreateErpOrder"
           :disabled="!erpAccountForm.selectedAccount"
           :loading="erpAccountDialog.submitting"
@@ -454,9 +449,9 @@
       <el-table :data="phoneList" border style="width: 100%">
         <el-table-column prop="phone" label="手机号" align="center">
           <template slot-scope="scope">
-            <el-input 
-              v-if="scope.row.editing" 
-              v-model="scope.row.phone" 
+            <el-input
+              v-if="scope.row.editing"
+              v-model="scope.row.phone"
               placeholder="请输入手机号"
               @blur="validatePhone(scope.row)"
               @keyup.enter.native="handleSavePhone(scope.$index)"
@@ -466,27 +461,27 @@
         </el-table-column>
         <el-table-column label="操作" align="center" width="300">
           <template slot-scope="scope">
-            <el-button 
-              v-if="scope.row.editing" 
-              type="success" 
-              size="mini" 
+            <el-button
+              v-if="scope.row.editing"
+              type="success"
+              size="mini"
               @click="handleSavePhone(scope.$index)"
             >保存</el-button>
-            <el-button 
-              v-if="scope.row.editing" 
-              type="info" 
-              size="mini" 
+            <el-button
+              v-if="scope.row.editing"
+              type="info"
+              size="mini"
               @click="handleCancelEdit(scope.$index)"
             >取消</el-button>
-            <el-button 
-              v-if="!scope.row.editing" 
-              type="primary" 
-              size="mini" 
+            <el-button
+              v-if="!scope.row.editing"
+              type="primary"
+              size="mini"
               @click="handleEditPhone(scope.$index)"
             >修改</el-button>
-            <el-button 
-              type="danger" 
-              size="mini" 
+            <el-button
+              type="danger"
+              size="mini"
               @click="handleDeletePhone(scope.$index)"
             >删除</el-button>
           </template>
@@ -525,6 +520,82 @@
         <el-button @click="uploadStatus.open = false">取 消</el-button>
       </div>
     </el-dialog>
+
+    <!-- 导入结果对话框 -->
+    <el-dialog
+      title="导入结果"
+      :visible.sync="importResultDialog.open"
+      width="80%"
+      append-to-body
+      :close-on-click-modal="false"
+    >
+      <div class="import-result-summary">
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <div class="summary-item success">
+              <div class="summary-label">成功</div>
+              <div class="summary-value">{{ importResult.successNum || 0 }}</div>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="summary-item failure">
+              <div class="summary-label">失败</div>
+              <div class="summary-value">{{ importResult.failureNum || 0 }}</div>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="summary-item total">
+              <div class="summary-label">总计</div>
+              <div class="summary-value">{{ (importResult.successNum || 0) + (importResult.failureNum || 0) }}</div>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+
+      <el-tabs v-model="importResultTab" type="card">
+        <el-tab-pane label="成功列表" name="success">
+          <el-table
+            :data="displayedSuccessList"
+            border
+            stripe
+            height="400"
+            style="width: 100%"
+            v-loading="successListLoading"
+            @scroll="handleSuccessListScroll"
+          >
+            <el-table-column type="index" label="序号" width="60" align="center" />
+            <el-table-column prop="rowNum" label="Excel行号" width="120" align="center" />
+            <el-table-column prop="orderCode" label="订单号" align="center" />
+          </el-table>
+          <div v-if="displayedSuccessList.length < importResult.successList.length" class="load-more-tip">
+            已加载 {{ displayedSuccessList.length }} / {{ importResult.successList.length }} 条,滚动加载更多...
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="失败列表" name="failure">
+          <el-table
+            :data="displayedFailureList"
+            border
+            stripe
+            height="400"
+            style="width: 100%"
+            v-loading="failureListLoading"
+            @scroll="handleFailureListScroll"
+          >
+            <el-table-column type="index" label="序号" width="60" align="center" />
+            <el-table-column prop="rowNum" label="Excel行号" width="120" align="center" />
+            <el-table-column prop="orderCode" label="订单号" align="center" />
+            <el-table-column prop="errorMessage" label="错误信息" align="center" show-overflow-tooltip />
+          </el-table>
+          <div v-if="displayedFailureList.length < importResult.failureList.length" class="load-more-tip">
+            已加载 {{ displayedFailureList.length }} / {{ importResult.failureList.length }} 条,滚动加载更多...
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="importResultDialog.open = false">关 闭</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
@@ -663,6 +734,28 @@ export default {
         // 上传的地址
         url: process.env.VUE_APP_BASE_API + "/his/integralOrder/importOrderStatusData"
       },
+      // 导入结果对话框
+      importResultDialog: {
+        open: false
+      },
+      // 导入结果数据
+      importResult: {
+        successNum: 0,
+        failureNum: 0,
+        successList: [],
+        failureList: []
+      },
+      // 导入结果标签页
+      importResultTab: "success",
+      // 分片加载相关
+      displayedSuccessList: [],
+      displayedFailureList: [],
+      successListPageSize: 50,
+      failureListPageSize: 50,
+      successListCurrentPage: 1,
+      failureListCurrentPage: 1,
+      successListLoading: false,
+      failureListLoading: false,
     };
   },
   created() {
@@ -687,13 +780,121 @@ export default {
           this.uploadStatus.isUploading = true;
         },
     // 文件上传成功处理
-         handleFileSuccessOrder(response, file, fileList) {
-          this.uploadStatus.open = false;
-          this.uploadStatus.isUploading = false;
-          this.$refs.uploadStatus.clearFiles();
-          this.$alert(response.msg, "导入结果", { dangerouslyUseHTMLString: true });
-          this.getList();
-        },
+    handleFileSuccessOrder(response, file, fileList) {
+      this.uploadStatus.open = false;
+      this.uploadStatus.isUploading = false;
+      this.$refs.uploadStatus.clearFiles();
+
+      // 处理新的返回结果格式
+      if (response.code === 200 && response.data) {
+        this.importResult = response.data;
+        // 初始化显示列表
+        this.displayedSuccessList = [];
+        this.displayedFailureList = [];
+        this.successListCurrentPage = 1;
+        this.failureListCurrentPage = 1;
+
+        // 加载第一页数据
+        this.loadSuccessListPage(1);
+        this.loadFailureListPage(1);
+
+        // 显示结果对话框
+        this.importResultDialog.open = true;
+        this.importResultTab = this.importResult.failureNum > 0 ? "failure" : "success";
+      } else {
+        this.$alert(response.msg || "导入失败", "导入结果", { dangerouslyUseHTMLString: true });
+      }
+
+      this.getList();
+    },
+    // 加载成功列表分页数据
+    loadSuccessListPage(page) {
+      if (this.successListLoading) return;
+      this.successListLoading = true;
+
+      const start = (page - 1) * this.successListPageSize;
+      const end = start + this.successListPageSize;
+      const pageData = this.importResult.successList.slice(start, end);
+
+      this.displayedSuccessList = this.displayedSuccessList.concat(pageData);
+      this.successListCurrentPage = page;
+
+      this.$nextTick(() => {
+        this.successListLoading = false;
+        // 绑定滚动事件
+        if (page === 1) {
+          this.bindScrollEvents();
+        }
+      });
+    },
+    // 加载失败列表分页数据
+    loadFailureListPage(page) {
+      if (this.failureListLoading) return;
+      this.failureListLoading = true;
+
+      const start = (page - 1) * this.failureListPageSize;
+      const end = start + this.failureListPageSize;
+      const pageData = this.importResult.failureList.slice(start, end);
+
+      this.displayedFailureList = this.displayedFailureList.concat(pageData);
+      this.failureListCurrentPage = page;
+
+      this.$nextTick(() => {
+        this.failureListLoading = false;
+        // 绑定滚动事件
+        if (page === 1) {
+          this.bindScrollEvents();
+        }
+      });
+    },
+    // 绑定滚动事件
+    bindScrollEvents() {
+      this.$nextTick(() => {
+        // 成功列表滚动
+        const successTableBody = document.querySelector('.el-dialog__body .el-tabs__content .el-tab-pane:first-child .el-table__body-wrapper');
+        if (successTableBody) {
+          successTableBody.removeEventListener('scroll', this.handleSuccessListScroll);
+          successTableBody.addEventListener('scroll', this.handleSuccessListScroll);
+        }
+
+        // 失败列表滚动
+        const failureTableBody = document.querySelector('.el-dialog__body .el-tabs__content .el-tab-pane:last-child .el-table__body-wrapper');
+        if (failureTableBody) {
+          failureTableBody.removeEventListener('scroll', this.handleFailureListScroll);
+          failureTableBody.addEventListener('scroll', this.handleFailureListScroll);
+        }
+      });
+    },
+    // 成功列表滚动加载
+    handleSuccessListScroll(event) {
+      const container = event.target;
+      const scrollTop = container.scrollTop;
+      const scrollHeight = container.scrollHeight;
+      const clientHeight = container.clientHeight;
+
+      // 距离底部50px时加载下一页
+      if (scrollHeight - scrollTop - clientHeight < 50) {
+        const totalPages = Math.ceil(this.importResult.successList.length / this.successListPageSize);
+        if (this.successListCurrentPage < totalPages && !this.successListLoading) {
+          this.loadSuccessListPage(this.successListCurrentPage + 1);
+        }
+      }
+    },
+    // 失败列表滚动加载
+    handleFailureListScroll(event) {
+      const container = event.target;
+      const scrollTop = container.scrollTop;
+      const scrollHeight = container.scrollHeight;
+      const clientHeight = container.clientHeight;
+
+      // 距离底部50px时加载下一页
+      if (scrollHeight - scrollTop - clientHeight < 50) {
+        const totalPages = Math.ceil(this.importResult.failureList.length / this.failureListPageSize);
+        if (this.failureListCurrentPage < totalPages && !this.failureListLoading) {
+          this.loadFailureListPage(this.failureListCurrentPage + 1);
+        }
+      }
+    },
     importUpdateOrderTemplate(){
         getIntegralTemplate().then(response => {
           this.download(response.msg);
@@ -759,15 +960,98 @@ export default {
         }, 1);
     },
     /** 查询积分商品订单列表 */
+    /** 查询积分商品订单列表 */
     getList() {
       this.loading = true;
-      
+
+      // 直接传递订单编号数组给后端
+      listIntegralOrder(this.queryParams).then(response => {
+        // 解析itemJson字段,提取goodsName和图片
+        const processedData = response.rows.map(item => {
+          let goodsName = '';
+          let goodsImage = '';
+          let num = '';
+          try {
+            if (item.itemJson) {
+              const itemData = JSON.parse(item.itemJson);
+
+              // 如果itemJson是数组格式,遍历数组并用换行分隔每个商品
+              if (Array.isArray(itemData) && itemData.length > 0) {
+                // 提取所有商品的信息,每个商品一行
+                const goodsInfoList = itemData
+                  .filter(goods => goods.goodsName)
+                  .map(goods => {
+                    const name = goods.goodsName || '';
+                    const itemNum = goods.num || 1; // 如果没有数量,默认1
+                    return `${name},数量:${itemNum}个`;
+                  });
+
+                // 如果有多个商品,用换行符分隔
+                if (goodsInfoList.length > 0) {
+                  goodsName = goodsInfoList.join('\n');
+                } else if (itemData[0].goodsName) {
+                  // 如果没有商品名称但有第一个商品对象,使用第一个
+                  const firstItem = itemData[0];
+                  goodsName = `${firstItem.goodsName || ''},数量:${firstItem.num || 1}个`;
+                }
+
+                // 提取第一个商品的图片
+                const firstItem = itemData[0];
+                if (firstItem.imgUrl) {
+                  goodsImage = firstItem.imgUrl;
+                } else if (firstItem.images && firstItem.images.split(',').length > 0) {
+                  goodsImage = firstItem.images.split(',')[0];
+                }
+              }
+              // 如果itemJson是对象格式,直接取goodsName和图片
+              else if (itemData.goodsName) {
+                goodsName = `${itemData.goodsName},数量:${itemData.num || 1}个`;
+
+                // 提取图片,优先使用imgUrl,如果没有则使用images的第一张
+                if (itemData.imgUrl) {
+                  goodsImage = itemData.imgUrl;
+                } else if (itemData.images && itemData.images.split(',').length > 0) {
+                  goodsImage = itemData.images.split(',')[0];
+                }
+              }
+            }
+          } catch (error) {
+            console.error('解析itemJson失败:', error);
+            goodsName = '';
+            goodsImage = '';
+            num = '';
+          }
+
+          return {
+            ...item,
+            goodsName: goodsName,
+            goodsImage: goodsImage,
+            num: num
+          };
+        });
+
+        this.integralOrderList = processedData;
+        this.total = response.total;
+        this.loading = false;
+      }).catch(error => {
+        console.error('查询订单列表失败:', error);
+        this.loading = false;
+        this.$message.error('查询订单列表失败');
+      });
+    },
+
+    /** 查询积分商品订单列表 */
+    //原代码 先留着
+    getList0() {
+      this.loading = true;
+
       // 直接传递订单编号数组给后端
       listIntegralOrder(this.queryParams).then(response => {
         // 解析itemJson字段,提取goodsName和图片
         const processedData = response.rows.map(item => {
           let goodsName = '';
           let goodsImage = '';
+          let num = '';
           try {
             if (item.itemJson) {
               const itemData = JSON.parse(item.itemJson);
@@ -796,15 +1080,17 @@ export default {
             console.error('解析itemJson失败:', error);
             goodsName = '';
             goodsImage = '';
+            num ='';
           }
-          
+
           return {
             ...item,
             goodsName: goodsName,
-            goodsImage: goodsImage
+            goodsImage: goodsImage,
+            num: num
           };
         });
-        
+
         this.integralOrderList = processedData;
         this.total = response.total;
         this.loading = false;
@@ -856,12 +1142,12 @@ export default {
       this.queryParams.qwUserId=null;
       this.queryParams.companyId=null;
       this.queryParams.companyUserId=null;
-      
+
       // 清除订单号标签
       this.queryParams.orderCodes = [];
       this.currentInput = '';
       this.inputVisible = false;
-      
+
       this.handleQuery();
 
     },
@@ -963,7 +1249,7 @@ export default {
       this.getErpPhoneList(); // 加载手机号列表
       this.updateOrderSummary();
     },
-    
+
     // 加载ERP账户数据
     async loadErpAccountData() {
       try {
@@ -973,7 +1259,7 @@ export default {
         console.error('加载ERP账户失败:', error);
       }
     },
-    
+
     // 更新订单统计
     updateOrderSummary() {
       // 如果没有选择任何数据,则使用查询出来的所有数据
@@ -985,14 +1271,14 @@ export default {
         this.orderSummary.selectedCount = this.ids.length;
         this.orderSummary.queryCount = this.total;
       }
-      
+
       // 计算总金额(这里需要根据实际数据结构调整)
       this.calculateTotalAmount();
     },
     // 计算总金额
     calculateTotalAmount() {
       let totalAmount = 0;
-      
+
       if (this.ids.length === 0) {
         // 使用所有查询数据计算金额
         this.integralOrderList.forEach(order => {
@@ -1000,28 +1286,28 @@ export default {
         });
       } else {
         // 使用选中的数据计算金额
-        const selectedOrders = this.integralOrderList.filter(order => 
+        const selectedOrders = this.integralOrderList.filter(order =>
           this.ids.includes(order.orderId)
         );
         selectedOrders.forEach(order => {
           totalAmount += parseFloat(order.payMoney || 0);
         });
       }
-      
+
       this.orderSummary.totalAmount = totalAmount.toFixed(2);
     },
-    
+
     // 取消ERP账户对话框
     cancelErpAccountDialog() {
       this.erpAccountDialog.open = false;
       this.erpAccountForm.selectedAccount = null;
     },
-    
+
     // 确认创建ERP订单
     async confirmCreateErpOrder() {
       // 收集选中的orderId
       let selectedOrderIds = [];
-      
+
       if (this.ids.length === 0) {
         // 如果没有选择任何数据,使用查询出来的所有数据的orderId
         selectedOrderIds = this.integralOrderList.map(order => order.orderId);
@@ -1029,11 +1315,11 @@ export default {
         // 如果选择了数据,使用选中的orderId
         selectedOrderIds = this.ids;
       }
-      
+
       // 收集ERP账户和手机号
       const selectedErpAccount = this.erpAccountForm.selectedAccount;
       const selectedPhones = this.erpAccountForm.selectedPhones;
-      
+
       // 准备请求参数
       const params = {
         orderIds: selectedOrderIds,
@@ -1042,7 +1328,7 @@ export default {
       };
       try {
         this.erpAccountDialog.submitting = true;
-        
+
         // 根据弹窗标题判断是数据分拣还是创建ERP
         if (this.erpAccountDialog.title === "数据分拣") {
           // 调用数据分拣接口
@@ -1054,13 +1340,13 @@ export default {
           // console.log("参数:",params)
           this.$message.success('创建ERP成功');
         }
-        
+
         // 关闭弹窗
         this.cancelErpAccountDialog();
-        
+
         // 刷新列表
         this.getList();
-        
+
       } catch (error) {
         console.error('操作失败:', error);
         this.$message.error('操作失败');
@@ -1128,7 +1414,7 @@ export default {
         return;
       }
       // 检查是否重复
-      const duplicateIndex = this.phoneList.findIndex((item, idx) => 
+      const duplicateIndex = this.phoneList.findIndex((item, idx) =>
         idx !== index && item.phone === phone
       );
       if (duplicateIndex !== -1) {
@@ -1184,17 +1470,17 @@ export default {
         this.$message.error('请先保存正在编辑的手机号');
         return;
       }
-      
+
       // 检查是否有空的手机号
       const emptyPhone = this.phoneList.find(item => !item.phone.trim());
       if (emptyPhone) {
         this.$message.error('存在空的手机号,请删除或填写完整');
         return;
       }
-      
+
       // 构造手机号列表
       const phoneList = this.phoneList.map(item => item.phone);
-      
+
       // 调用保存接口
       saveErpPhone(phoneList).then(response => {
         if (response.code === 200) {
@@ -1219,13 +1505,13 @@ export default {
     // 处理键盘按下事件
     handleKeyDown(event) {
       const { key, target } = event
-      
+
       // 处理退格键删除标签
       if (key === 'Backspace' && !target.value && this.queryParams.orderCodes.length > 0) {
         event.preventDefault()
         this.removeOrderCode(this.queryParams.orderCodes.length - 1)
       }
-      
+
       // 处理分隔符
       if ([',', ',', ' ', 'Enter'].includes(key)) {
         event.preventDefault()
@@ -1236,7 +1522,7 @@ export default {
     // 处理键盘抬起事件(实时分割输入)
     handleKeyUp(event) {
       const value = event.target.value
-      
+
       // 检查是否包含分隔符
       if (/[,,\s]/.test(value)) {
         this.handleInputConfirm()
@@ -1246,35 +1532,35 @@ export default {
     // 确认输入
     handleInputConfirm() {
       const inputValue = this.currentInput.trim()
-      
+
       if (inputValue) {
         // 分割多个订单号
         const codes = inputValue.split(/[,,\s]+/).filter(code => code.trim())
-        
+
         codes.forEach(code => {
           this.addOrderCode(code.trim())
         })
       }
-      
+
       this.currentInput = ''
     },
 
     // 添加订单号
     addOrderCode(code) {
       if (!code) return
-      
+
       // 检查数量限制
       if (this.maxOrderCodes > 0 && this.queryParams.orderCodes.length >= this.maxOrderCodes) {
         this.$message.warning(`最多只能添加 ${this.maxOrderCodes} 个订单号`)
         return
       }
-      
+
       // 检查重复
       if (this.queryParams.orderCodes.includes(code)) {
         this.$message.warning(`订单号 "${code}" 已存在`)
         return
       }
-      
+
       // 添加到列表
       this.queryParams.orderCodes.push(code)
     },
@@ -1371,4 +1657,63 @@ export default {
   align-items: center;
   gap: 8px;
 }
+
+/* 导入结果样式 */
+.import-result-summary {
+  margin-bottom: 20px;
+  padding: 20px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+}
+
+.summary-item {
+  text-align: center;
+  padding: 15px;
+  border-radius: 4px;
+}
+
+.summary-item.success {
+  background-color: #f0f9ff;
+  border: 1px solid #b3d8ff;
+}
+
+.summary-item.failure {
+  background-color: #fef0f0;
+  border: 1px solid #fbc4c4;
+}
+
+.summary-item.total {
+  background-color: #f4f4f5;
+  border: 1px solid #d3d4d6;
+}
+
+.summary-label {
+  font-size: 14px;
+  color: #606266;
+  margin-bottom: 8px;
+}
+
+.summary-value {
+  font-size: 24px;
+  font-weight: bold;
+}
+
+.summary-item.success .summary-value {
+  color: #67c23a;
+}
+
+.summary-item.failure .summary-value {
+  color: #f56c6c;
+}
+
+.summary-item.total .summary-value {
+  color: #909399;
+}
+
+.load-more-tip {
+  text-align: center;
+  padding: 10px;
+  color: #909399;
+  font-size: 12px;
+}
 </style>

+ 527 - 0
src/views/his/statistics/courseReport.vue

@@ -0,0 +1,527 @@
+<template>
+  <div class="app-container">
+    <!-- 添加维度切换Tab -->
+    <el-tabs v-model="activeDimension" @tab-click="handleDimensionChange">
+      <el-tab-pane label="公司维度" name="company"></el-tab-pane>
+      <el-tab-pane label="课程维度" name="course"></el-tab-pane>
+      <el-tab-pane label="小节维度" name="video"></el-tab-pane>
+    </el-tabs>
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="85px">
+      <el-form-item label="公司名" prop="companyId" v-if="activeDimension === 'company'">
+        <el-select filterable v-model="queryParams.companyId" placeholder="请选择公司名"
+                   clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课程" prop="courseId" v-if="activeDimension === 'course'||activeDimension === 'video'">
+        <el-select filterable v-model="queryParams.courseId" placeholder="请选择课程"
+                   clearable size="small" @change="handleCourseChange">
+          <el-option
+            v-for="item in courses"
+            :key="item.courseId"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="小节" prop="videoId" v-if="activeDimension === 'video' && queryParams.courseId">
+        <el-select filterable v-model="queryParams.videoId" placeholder="请选择小节"
+                   clearable size="small">
+          <el-option
+            v-for="item in videos"
+            :key="item.videoId"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-form-item label="看课时间" prop="createTime">
+          <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd"
+                          type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
+                          @change="xdChange"></el-date-picker>
+        </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 height="500" v-loading="loading" border :data="packageOrderList">
+      <el-table-column label="销售公司" align="center" prop="companyName" width="120px"
+                       v-if="activeDimension === 'company'"/>
+      <el-table-column label="课程名称" align="center" prop="courseName"
+                       v-if="activeDimension === 'course'"/>
+      <el-table-column label="小节名称" align="center" prop="videoName"
+                       v-if="activeDimension === 'video'"/>
+      <el-table-column label="进线人数" align="center" prop="accessCount"/>
+      <el-table-column label="完课人数" align="center" prop="finishedCount"/>
+      <el-table-column label="完播数" align="center" prop="courseCompleteTimes"/>
+      <el-table-column label="完课率" align="center" prop="finishRate"/>
+    </el-table>
+    <div class="total-summary">
+      <span class="total-title">总计:</span>
+      <span class="total-item">进线人数: {{ calculatedTotalData.accessCount }}</span>
+      <span class="total-item">完课人数: {{ calculatedTotalData.finishedCount }}</span>
+      <span class="total-item">完课率: {{ calculatedTotalData.finishRate}}</span>
+      <span class="total-item">完播数: {{ calculatedTotalData.courseCompleteTimes }}</span>
+    </div>
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </div>
+</template>
+
+<script>
+import {
+  listPackageOrder, getPackageOrder, delPackageOrder, addPackageOrder, updatePackageOrder, exportPackageOrder,
+  PackageOrderReport, orderReport, courseReport, exportOrderReport, exportCourseReport
+} from "@/api/his/packageOrder";
+import {getCompanyList} from "@/api/company/company";
+import packageOrderDetails from '../../components/his/packageOrderDetails.vue';
+import {treeselect} from "@/api/company/companyDept";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import {getTask} from "@/api/common";
+import {getCourseList, getVideosByCourse} from "@/api/course/userWatchCourseStatistics";
+
+export default {
+  name: "PackageOrder",
+  components: {packageOrderDetails, Treeselect},
+  data() {
+    return {
+      normalizer: function(node) {
+        return {
+          id: node.id || node.dictValue,
+          label: node.label || node.dictLabel,
+          children: node.children
+        }
+      },
+      // 添加用于存储计算总和的数据
+      calculatedTotalData: {
+        accessCount: 0,
+        finishedCount: 0,
+        finishRate: '0%',
+        courseCompleteTimes: 0,
+      },
+      totalData: {},
+      companys: [],
+      courses: [],  // 课程列表
+      videos: [],   // 小节列表
+      activeDimension: 'company', // 当前激活的维度
+      companyId: undefined,
+      show: {
+        open: false,
+      },
+      sourceOptions: [],
+      actName: "2",
+      // 遮罩层
+      loading: true,
+      startTime: null,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      createTime: null,
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      endTime: null,
+      // 总条数
+      total: 0,
+      // 套餐订单表格数据
+      packageOrderList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 是否支付字典
+      isPayOptions: [],
+      // 状态字典
+      statusOptions: [],
+      refundStatusOptions: [],
+      packageSubTypeOptions: [],
+      payTypeOptions: [],
+      deliveryPayStatusOptions: [],
+      deliveryStatusOptions: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        orderSn: null,
+        userId: null,
+        doctorId: null,
+        doctorName: null,
+        phone: null,
+        phoneMk: null,
+        packageId: null,
+        packageName: null,
+        payMoney: null,
+        isPay: null,
+        days: null,
+        status: null,
+        startTime: null,
+        startDate: null,
+        endDate:null,
+        finishTime: null,
+        sTime: null,
+        eTime: null,
+        stTime: null,
+        endTime: null,
+        endStartTime: null,
+        endEndTime: null,
+        companyUserName: null,
+        companyName: null,
+        deptId: null,
+        source: null,
+        dimension: 'company',  // 添加维度参数
+        courseId: null,    // 课程ID
+        videoId: null     // 小节ID
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {}
+    };
+  },
+  created() {
+    // 设置默认时间为前一天
+    const yesterday = new Date();
+    yesterday.setDate(yesterday.getDate() - 1);
+    const formatDate = (date) => {
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    };
+    this.createTime = [formatDate(yesterday), formatDate(yesterday)];
+    this.queryParams.sTime = this.createTime[0];
+    this.queryParams.eTime = this.createTime[1];
+    // 获取课程列表
+    getCourseList().then(response => {
+      this.courses = response.data;
+    })
+    getCompanyList().then(response => {
+      this.companys = response.data;
+      if (this.companys != null && this.companys.length > 0) {
+        this.companyId = this.companys[0].companyId;
+      }
+      this.companys.push({companyId: "-1", companyName: "无"})
+    });
+    this.getList();
+  },
+  methods: {
+    /** 查询套餐订单列表 */
+    getList() {
+      this.loading = true;
+      this.queryParams.dimension = this.activeDimension;
+      let requestParams = { ...this.queryParams };
+      if (this.activeDimension === 'video') {
+        // 在小节维度下,如果已选择小节,只保留videoId,不传递courseId
+        if (requestParams.videoId) {
+          delete requestParams.courseId;
+        }
+      }
+      courseReport(requestParams).then(response => {
+        const rows = response.rows || [];
+        // 标准化数据,为缺失字段提供默认值
+        this.packageOrderList = rows.map(item => ({
+          ...item,
+          // 确保必需字段存在,如果缺失则提供默认值
+          companyName: item.companyName || '-',
+          courseName: item.courseName || '-',
+          videoName: item.videoName || '-',
+          accessCount: item.accessCount || 0,
+          finishedCount: item.finishedCount || 0,
+          courseCompleteTimes: item.courseCompleteTimes || 0,
+          finishRate: item.finishRate || '0%',
+        }));
+        console.log("列表数据:", this.packageOrderList);
+        this.total = response.total;
+        this.calculateTotals();
+      }).catch(error => {
+        // 即使接口返回错误,也要重置加载状态
+        console.error('获取数据失败:', error);
+        this.packageOrderList = [];
+        this.total = 0;
+      }).finally(() => {
+        // 无论成功或失败,都重置加载状态
+        this.loading = false;
+        // 延迟强制更新以确保DOM完全渲染
+        setTimeout(() => {
+          this.$forceUpdate();
+        }, 100);
+      });
+    },
+    calculateTotals() {
+      // 重置总计数据
+      this.calculatedTotalData = {
+        accessCount: 0,
+        finishedCount: 0,
+        courseCompleteTimes: 0,
+        finishRate: '0%',
+      };
+
+      // 遍历当前页数据计算总和
+      this.packageOrderList.forEach(item => {
+        this.calculatedTotalData.accessCount += Number(item.accessCount) || 0;
+        this.calculatedTotalData.finishedCount += Number(item.finishedCount) || 0;
+        this.calculatedTotalData.courseCompleteTimes += Number(item.courseCompleteTimes) || 0;
+      });
+
+      if (this.calculatedTotalData.accessCount > 0) {
+        // 完课率 = 完课人数 / 进线人数
+        this.calculatedTotalData.finishRate = ((this.calculatedTotalData.finishedCount / this.calculatedTotalData.accessCount) * 100).toFixed(2) + '%';
+      } else {
+        this.calculatedTotalData.finishRate = '0.00%';
+      }
+    },
+    /** 维度切换处理 */
+    handleDimensionChange(tab) {
+      this.activeDimension = tab.name;
+      // 更新查询参数中的维度
+      this.queryParams.dimension = tab.name;
+
+      // 重置相关查询参数
+      if (this.activeDimension === 'company') {
+        // 公司维度,清空课程和小节参数
+        this.queryParams.courseId = null;
+        this.queryParams.videoId = null;
+      } else if (this.activeDimension === 'course') {
+        // 课程维度,清空公司和小节参数
+        this.queryParams.companyId = null;
+        this.queryParams.videoId = null;
+      } else if (this.activeDimension === 'video') {
+        // 保留 courseId 的选择状态,但清空 videoId
+        this.queryParams.videoId = null;
+      }
+
+      // 清空小节列表
+      this.videos = [];
+      // 重新获取数据
+      this.getList();
+    },
+    /** 课程变更处理 */
+    handleCourseChange(val) {
+      // 在课程维度和小节维度都需要处理课程变更
+      if (this.activeDimension === 'course' || this.activeDimension === 'video') {
+        this.queryParams.courseId = val;
+        this.queryParams.videoId = null; // 清空已选择的小节
+
+        if (val) {
+          // 根据课程ID获取对应的小节列表
+          this.getVideosByCourseId(val);
+        } else {
+          // 如果清空课程,也清空小节选项
+          this.videos = [];
+        }
+      }
+    },
+
+    /** 根据训练营获取营期数据 */
+    getVideosByCourseId(courseId) {
+      getVideosByCourse(courseId).then((response) => {
+        this.videos = response.data || [];
+      }).catch(error => {
+        console.error('获取小节数据失败:', error);
+        this.videos = [];
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        orderId: null,
+        orderSn: null,
+        userId: null,
+        doctorId: null,
+        packageId: null,
+        packageName: null,
+        payMoney: null,
+        isPay: null,
+        days: null,
+        status: 0,
+        startTime: null,
+        finishTime: null,
+        createTime: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      // 清空所有时间相关变量
+      this.createTime = null;
+      this.startTime = null;
+      this.endTime = null;
+
+      // 重置所有查询参数
+      this.queryParams = {
+        pageNum: null,
+        pageSize: null,
+        orderSn: null,
+        userId: null,
+        doctorId: null,
+        doctorName: null,
+        phone: null,
+        phoneMk: null,
+        packageId: null,
+        packageName: null,
+        payMoney: null,
+        isPay: null,
+        days: null,
+        status: null,
+        startTime: null,
+        finishTime: null,
+        sTime: null,
+        eTime: null,
+        stTime: null,
+        endTime: null,
+        endStartTime: null,
+        endEndTime: null,
+        companyUserName: null,
+        companyName: null,
+        deptId: null,
+        source: null,
+        dimension: this.activeDimension,  // 维持当前维度
+        companyId: null,  // 重置所有维度ID
+        courseId: null,
+        videoId: null,
+      };
+      // 重新获取课程列表
+      getCourseList().then(response => {
+        this.courses = response.data;
+      });
+      this.videos = [];
+      // 立即执行查询
+      this.handleQuery();
+    },
+    xdChange() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出完课统计报表', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportCourseReport(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+      });
+    },
+    endChange() {
+      if (this.endTime != null) {
+        this.queryParams.endStartTime = this.endTime[0];
+        this.queryParams.endEndTime = this.endTime[1];
+      } else {
+        this.queryParams.endStartTime = null;
+        this.queryParams.endEndTime = null;
+      }
+    }
+  }
+};
+</script>
+
+<style scoped>
+.total-summary {
+  margin-top: 15px;
+  padding: 15px 20px;
+  background: linear-gradient(135deg, #f5f7fa 0%, #e4e7f4 100%);
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+
+.total-title {
+  font-weight: bold;
+  font-size: 16px;
+  color: #303133;
+  margin-right: 20px;
+  flex-shrink: 0;
+}
+
+.total-item {
+  margin-right: 25px;
+  padding: 5px 10px;
+  background: white;
+  border-radius: 3px;
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
+  display: inline-block;
+  margin-bottom: 5px;
+  font-size: 13px;
+  color: #606266;
+}
+
+.total-item::before {
+  content: "";
+  display: inline-block;
+  width: 3px;
+  height: 3px;
+  background: #409eff;
+  border-radius: 50%;
+  margin-right: 5px;
+  vertical-align: middle;
+}
+
+/* 响应式处理 */
+@media (max-width: 768px) {
+  .total-summary {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+
+  .total-title {
+    margin-bottom: 10px;
+  }
+
+  .total-item {
+    margin-right: 10px;
+    margin-bottom: 8px;
+  }
+}
+</style>

+ 187 - 0
src/views/his/storePayment/errorIndex.vue

@@ -0,0 +1,187 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
+<!--      <el-form-item label="订单编号" prop="businessCode">-->
+<!--        <el-input-->
+<!--          v-model="queryParams.businessCode"-->
+<!--          placeholder="请输入订单号"-->
+<!--          clearable-->
+<!--          size="small"-->
+<!--          @keyup.enter.native="handleQuery"-->
+<!--        />-->
+<!--      </el-form-item>-->
+
+      <el-form-item label="外部订单" prop="tradeNo">
+        <el-input
+          v-model="queryParams.tradeNo"
+          placeholder="请输入外部订单号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+
+
+      <el-form-item label="业务类型" prop="businessType">
+        <el-select v-model="queryParams.businessType" placeholder="请选择业务类型" clearable size="small">
+          <el-option
+            v-for="dict in busineOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </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-table height="600" v-loading="loading" border :data="storePaymentList" @selection-change="handleSelectionChange">
+<!--      <el-table-column type="selection" width="55" align="center" />-->
+      <el-table-column label="订单编号" align="center" prop="orderNo" width="170px"/>
+      <el-table-column label="外部单号" align="center" prop="orderFlowNo" width="350px"/>
+      <el-table-column label="业务类型 " align="center" prop="businessType" width="100">
+        <template slot-scope="scope">
+          <dict-tag :options="busineOptions" :value="scope.row.businessType"/>
+        </template>
+      </el-table-column>
+<!--      <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="msg" />
+      <el-table-column label="创建时间" align="center" prop="createTime" width="155" />
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { listStorePaymentError } from "@/api/his/storePayment";
+export default {
+  name: "StoreErrorPayment",
+  data() {
+    return {
+      busineOptions:[],
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 支付明细表格数据
+      storePaymentList: [],
+      payModeOptions:[],
+      shareOptions:[],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 状态 0未处理 1已处理
+      statusOptions: [
+        {dictValue:'0',dictLabel:"未处理"},
+        {dictValue:'1',dictLabel:"已处理"}
+      ],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        businessType: null,
+        status: '0',
+
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+
+    this.getDicts("sys_store_payment_business_type").then(response => {
+      this.busineOptions = response.data;
+    });
+  },
+  methods: {
+    /** 查询支付明细列表 */
+    getList() {
+      this.loading = true;
+      listStorePaymentError(this.queryParams).then(response => {
+        this.storePaymentList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        paymentId: null,
+        payCode: null,
+        payTypeCode: null,
+        payMoney: null,
+        payTime: null,
+        createTime: null,
+        tradeNo: null,
+        userId: null,
+        openId: null,
+        businessType: null,
+        businessId: null,
+        status: 0,
+        remark: null,
+        bankTransactionId: null,
+        bankSerialNo: null,
+        refundMoney: null,
+        refundTime: null,
+        storeId: 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.paymentId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    }
+  }
+};
+</script>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است