ソースを参照

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	src/views/company/wxAccount/index.vue
吴树波 1 時間 前
コミット
2ca8517dcd
59 ファイル変更10684 行追加85 行削除
  1. 1 1
      .env.prod-mengniu
  2. 25 0
      .env.prod-sxsm
  3. 1 0
      package.json
  4. 8 0
      src/api/company/companyVoiceRobotic.js
  5. 8 0
      src/api/company/companyWorkflow.js
  6. 142 0
      src/api/company/inboundCallManage.js
  7. 7 0
      src/api/course/courseRedPacketLog.js
  8. 129 0
      src/api/decoration/decoration.js
  9. 71 0
      src/api/fastGpt/fastGptChatQuestionStatistics.js
  10. 53 0
      src/api/fastGpt/fastgptChatQuestion.js
  11. 9 1
      src/api/system/config.js
  12. BIN
      src/assets/logo/sxsm.jpg
  13. 16 0
      src/components/decoration/BannerPreview.vue
  14. 15 0
      src/components/decoration/BottomCtaPreview.vue
  15. 15 0
      src/components/decoration/BrandStoryPreview.vue
  16. 16 0
      src/components/decoration/CouponCardPreview.vue
  17. 18 0
      src/components/decoration/GoodsListPreview.vue
  18. 16 0
      src/components/decoration/NoticeBarPreview.vue
  19. 25 0
      src/router/index.js
  20. 2 2
      src/views/company/aiModel/account/info.vue
  21. 672 0
      src/views/company/aiModel/inboundCallManage/inboundCallRecord.vue
  22. 709 0
      src/views/company/aiModel/inboundCallManage/index.vue
  23. 70 2
      src/views/company/companyConfig/index.vue
  24. 176 1
      src/views/company/companyVoiceRobotic/index.vue
  25. 2308 0
      src/views/company/companyVoiceRobotic/myIndex.vue
  26. 32 0
      src/views/company/companyWorkflow/design.vue
  27. 2 0
      src/views/company/companyWorkflow/index.vue
  28. 657 0
      src/views/company/companyWorkflow/myIndex.vue
  29. 0 8
      src/views/company/wxAccount/index.vue
  30. 54 3
      src/views/components/QwUserSelect.vue
  31. 24 5
      src/views/course/courseWatchLog/deptWatchLog.vue
  32. 31 4
      src/views/course/courseWatchLog/index.vue
  33. 9 5
      src/views/course/courseWatchLog/myCourseWatchLog.vue
  34. 7 3
      src/views/course/courseWatchLog/statistics.vue
  35. 25 6
      src/views/course/courseWatchLog/watchLog.vue
  36. 2 2
      src/views/course/courseWatchLog/watchLogStatistics.vue
  37. 10 0
      src/views/crm/components/CustomerSelect.vue
  38. 272 0
      src/views/decoration/componentList.vue
  39. 810 0
      src/views/decoration/templateEditor.vue
  40. 187 0
      src/views/decoration/templateList.vue
  41. 393 0
      src/views/fastGpt/fastGptChatQuestion/index.vue
  42. 220 0
      src/views/fastGpt/fastGptChatQuestionStatistics/index.vue
  43. 22 0
      src/views/hisStore/components/productOrder.vue
  44. 2 0
      src/views/hisStore/storeOrder/healthStoreList.vue
  45. 18 18
      src/views/hisStore/storeOrder/index.vue
  46. 9 1
      src/views/qw/externalContact/index.vue
  47. 2 2
      src/views/qw/friendWelcome/indexNew.vue
  48. 11 0
      src/views/qw/sop/addSop.vue
  49. 6 1
      src/views/qw/sop/deptSop.vue
  50. 6 1
      src/views/qw/sop/mySop.vue
  51. 8 1
      src/views/qw/sop/sop.vue
  52. 9 0
      src/views/qw/sop/updateSop.vue
  53. 19 4
      src/views/qw/sopLogs/sopLogsList.vue
  54. 15 4
      src/views/qw/sopTemp/deptIndex.vue
  55. 16 4
      src/views/qw/sopTemp/index.vue
  56. 17 5
      src/views/qw/sopTemp/myIndex.vue
  57. 3275 0
      src/views/qw/sopTemp/updateSopTempSXJZ.vue
  58. 1 1
      src/views/qw/user/index.vue
  59. 1 0
      src/views/taskStatistics/callLog/index.vue

+ 1 - 1
.env.prod-mengniu

@@ -1,5 +1,5 @@
 # 页面标题
-VUE_APP_TITLE =蒙牛SCRM销售端
+VUE_APP_TITLE = 销售管理系统
 # 公司名称
 VUE_APP_COMPANY_NAME =云联融智互联网医院有限公司
 # ICP备案号

+ 25 - 0
.env.prod-sxsm

@@ -0,0 +1,25 @@
+# 页面标题
+VUE_APP_TITLE =三猫SCRM客服系统
+# 公司名称
+VUE_APP_COMPANY_NAME =西安挑宝益康医药连锁有限公司丈八西路第二分公司
+# ICP备案号
+VUE_APP_ICP_RECORD =陕ICP备2023000555号-8
+# ICP网站访问地址
+VUE_APP_ICP_URL =https://beian.miit.gov.cn
+# 网站LOG
+VUE_APP_LOG_URL =@/assets/logo/sxsm.jpg
+
+# 生产环境配置
+ENV = 'production'
+
+# FS管理系统/开发环境
+VUE_APP_BASE_API = '/prod-api'
+
+#默认 1、会员 2、企微
+VUE_APP_COURSE_DEFAULT = 2
+
+#项目所属
+VUE_APP_PROJECT_FROM=sxsm
+
+# 路由懒加载
+VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "build:prod-hcl": "vue-cli-service build --mode prod-hcl",
     "build:prod-test": "vue-cli-service build --mode prod-test",
     "build:prod-sxjz": "vue-cli-service build --mode prod-sxjz",
+    "build:prod-sxsm": "vue-cli-service build --mode prod-sxsm",
     "build:prod-sxtb": "vue-cli-service build --mode prod-sxtb",
     "build:prod-jnmy": "vue-cli-service build --mode prod-jnmy",
     "build:prod-knt": "vue-cli-service build --mode prod-knt",

+ 8 - 0
src/api/company/companyVoiceRobotic.js

@@ -8,6 +8,14 @@ export function listRobotic(query) {
     params: query
   })
 }
+
+export function myListRobotic(query) {
+    return request({
+        url: '/company/companyVoiceRobotic/myList',
+        method: 'get',
+        params: query
+    })
+}
 // 查询机器人外呼任务列表
 export function listAll(query) {
   return request({

+ 8 - 0
src/api/company/companyWorkflow.js

@@ -9,6 +9,14 @@ export function listWorkflow(query) {
   })
 }
 
+export function myListWorkflow(query) {
+    return request({
+        url: '/company/companyWorkflow/myList',
+        method: 'get',
+        params: query
+    })
+}
+
 // 查询AI工作流详细
 export function getWorkflow(workflowId) {
   return request({

+ 142 - 0
src/api/company/inboundCallManage.js

@@ -0,0 +1,142 @@
+import request from '@/utils/request'
+
+/**
+ * 查询呼入大模型配置列表
+ * @param {Object} query 查询参数
+ */
+export function listInboundLlm(query) {
+  return request({
+    url: '/company/inboundCallManage/list',
+    method: 'get',
+    params: query
+  })
+}
+
+/**
+ * 新增呼入大模型配置
+ * @param {Object} data 配置数据
+ */
+export function addInboundLlm(data) {
+  return request({
+    url: '/company/inboundCallManage',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 修改呼入大模型配置
+ * @param {Object} data 配置数据
+ */
+export function updateInboundLlm(data) {
+  return request({
+    url: '/company/inboundCallManage',
+    method: 'put',
+    data: data
+  })
+}
+
+/**
+ * 删除呼入大模型配置
+ * @param {String} ids 配置ID,多个用逗号分隔
+ */
+export function delInboundLlm(ids) {
+  return request({
+    url: '/company/inboundCallManage/' + ids,
+    method: 'delete'
+  })
+}
+
+/**
+ * 获取大模型账户下拉列表
+ */
+export function listLlmAccount() {
+  return request({
+    url: '/company/inboundCallManage/llmAccountList',
+    method: 'get'
+  })
+}
+
+/**
+ * 校验被叫号码是否唯一
+ */
+export function checkCallee(id, callee) {
+  return request({
+    url: '/company/inboundCallManage/checkCallee',
+    method: 'get',
+    params: { id, callee }
+  })
+}
+
+/**
+ * 获取ASR提供商列表
+ */
+export function listAsrProvider() {
+  return request({
+    url: '/company/inboundCallManage/asrProviderList',
+    method: 'get'
+  })
+}
+
+/**
+ * 获取TTS音色来源列表
+ */
+export function listVoiceSource() {
+  return request({
+    url: '/company/inboundCallManage/voiceSourceList',
+    method: 'get'
+  })
+}
+
+/**
+ * 根据音色来源获取音色列表
+ */
+export function listVoiceBySource(voiceSource) {
+  return request({
+    url: '/company/inboundCallManage/voiceList',
+    method: 'get',
+    params: { voiceSource }
+  })
+}
+
+/**
+ * 获取业务组列表
+ */
+export function listBizGroup() {
+  return request({
+    url: '/company/inboundCallManage/bizGroupList',
+    method: 'get'
+  })
+}
+
+/**
+ * 获取出局网关列表
+ */
+export function listGateway() {
+  return request({
+    url: '/company/inboundCallManage/gatewayList',
+    method: 'get'
+  })
+}
+
+/**
+ * 获取IVR列表
+ */
+export function listIvr() {
+  return request({
+    url: '/company/inboundCallManage/ivrList',
+    method: 'get'
+  })
+}
+
+/**
+ * 查询呼入通话记录列表
+ * @param {Object} query 查询参数
+ */
+export function listInboundCdr(query) {
+  return request({
+    url: '/company/inboundCallManage/inboundCdrList',
+    method: 'get',
+    params: query
+  })
+}

+ 7 - 0
src/api/course/courseRedPacketLog.js

@@ -43,6 +43,13 @@ export function videoList(id) {
     method: 'get'
   })
 }
+
+export function videoListByWatch(id) {
+    return request({
+        url: '/course/courseRedPacketLog/videoListByWatch/' + id,
+        method: 'get'
+    })
+}
 // 新增短链课程看课记录
 export function addCourseRedPacketLog(data) {
   return request({

+ 129 - 0
src/api/decoration/decoration.js

@@ -0,0 +1,129 @@
+import request from '@/utils/request'
+
+// ==================== 组件类型 ====================
+
+/**
+ * 查询组件类型下拉
+ */
+export function listComponentTypeOptions() {
+  return request({
+    url: '/decoration/componentType/options',
+    method: 'get'
+  })
+}
+
+// ==================== 组件管理 ====================
+
+/**
+ * 查询组件列表
+ */
+export function listDecorationComponent(query) {
+  return request({
+    url: '/decoration/component/list',
+    method: 'get',
+    params: query
+  })
+}
+
+/**
+ * 查询组件详情
+ */
+export function getDecorationComponent(id) {
+  return request({
+    url: '/decoration/component/' + id,
+    method: 'get'
+  })
+}
+
+/**
+ * 新增/修改组件
+ */
+export function saveDecorationComponent(data) {
+  return request({
+    url: '/decoration/component/save',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 删除组件
+ */
+export function delDecorationComponent(id) {
+  return request({
+    url: '/decoration/component/' + id,
+    method: 'delete'
+  })
+}
+
+/**
+ * 修改组件状态
+ */
+export function changeDecorationComponentStatus(data) {
+  return request({
+    url: '/decoration/component/changeStatus',
+    method: 'post',
+    data: data
+  })
+}
+
+// ==================== 模板管理 ====================
+
+/**
+ * 查询模板列表
+ */
+export function listDecorationTemplate() {
+  return request({
+    url: '/decoration/template/list',
+    method: 'get'
+  })
+}
+
+/**
+ * 查询模板详情
+ */
+export function getDecorationTemplate(id) {
+  return request({
+    url: '/decoration/template/' + id,
+    method: 'get'
+  })
+}
+
+/**
+ * 新增/修改模板
+ */
+export function saveDecorationTemplate(data) {
+  return request({
+    url: '/decoration/template/save',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 删除模板
+ */
+export function delDecorationTemplate(id) {
+  return request({
+    url: '/decoration/template/' + id,
+    method: 'delete'
+  })
+}
+
+// 当前公司使用某个模板
+export function useDecorationTemplate(data) {
+  return request({
+    url: '/decoration/companyTemplate/use',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询当前公司某个类型正在使用的模板
+export function getCurrentCompanyTemplate(query) {
+  return request({
+    url: '/decoration/companyTemplate/current',
+    method: 'get',
+    params: query
+  })
+}

+ 71 - 0
src/api/fastGpt/fastGptChatQuestionStatistics.js

@@ -0,0 +1,71 @@
+import request from '@/utils/request'
+
+// 查询高频聊天问题统计列表
+export function listFastGptChatQuestionStatistics(query) {
+  return request({
+    url: '/fastGpt/fastGptChatQuestionStatistics/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询高频聊天问题统计详细
+export function getFastGptChatQuestionStatistics(id) {
+  return request({
+    url: '/fastGpt/fastGptChatQuestionStatistics/' + id,
+    method: 'get'
+  })
+}
+
+// 新增高频聊天问题统计
+export function addFastGptChatQuestionStatistics(data) {
+  return request({
+    url: '/fastGpt/fastGptChatQuestionStatistics',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改高频聊天问题统计
+export function updateFastGptChatQuestionStatistics(data) {
+  return request({
+    url: '/fastGpt/fastGptChatQuestionStatistics',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除高频聊天问题统计
+export function delFastGptChatQuestionStatistics(id) {
+  return request({
+    url: '/fastGpt/fastGptChatQuestionStatistics/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出高频聊天问题统计
+export function exportFastGptChatQuestionStatistics(query) {
+  return request({
+    url: '/fastGpt/fastGptChatQuestionStatistics/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询高频聊天问题统计-问题明细列表(接口待定)
+export function listFastGptChatQuestionStatisticsDetail(query) {
+  return request({
+    url: '/fastGpt/fastGptChatQuestionStatistics/detail/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 新增高频聊天销售回复
+export function saveFastGptChatQuestionStatisticsSalesReply(data) {
+  return request({
+    url: '/fastGpt/fastGptChatQuestionStatistics/question/reply',
+    method: 'put',
+    data: data
+  })
+}

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

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询聊天问题收集列表
+export function listFastgptChatQuestion(query) {
+  return request({
+    url: '/fastGpt/fastgptChatQuestion/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询聊天问题收集详细
+export function getFastgptChatQuestion(id) {
+  return request({
+    url: '/fastGpt/fastgptChatQuestion/' + id,
+    method: 'get'
+  })
+}
+
+// 新增聊天问题收集
+export function addFastgptChatQuestion(data) {
+  return request({
+    url: '/fastGpt/fastgptChatQuestion',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改聊天问题收集
+export function updateFastgptChatQuestion(data) {
+  return request({
+    url: '/fastGpt/fastgptChatQuestion',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除聊天问题收集
+export function delFastgptChatQuestion(id) {
+  return request({
+    url: '/fastGpt/fastgptChatQuestion/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出聊天问题收集
+export function exportFastgptChatQuestion(query) {
+  return request({
+    url: '/fastGpt/fastgptChatQuestion/export',
+    method: 'get',
+    params: query
+  })
+}

+ 9 - 1
src/api/system/config.js

@@ -23,6 +23,7 @@ export function getConfigByKey(configKey) {
     method: 'get'
   })
 }
+
 // 根据参数键名查询参数值
 export function getConfigKey(configKey) {
   return request({
@@ -31,6 +32,13 @@ export function getConfigKey(configKey) {
   })
 }
 
+export function getCourseConfigByRewardType() {
+    return request({
+        url: '/system/config/getCourseConfigByRewardType',
+        method: 'get'
+    })
+}
+
 // 新增参数配置
 export function addConfig(data) {
   return request({
@@ -81,4 +89,4 @@ export function updateConfigByKey(data) {
     method: 'post',
     data: data
   })
-}
+}

BIN
src/assets/logo/sxsm.jpg


+ 16 - 0
src/components/decoration/BannerPreview.vue

@@ -0,0 +1,16 @@
+<template>
+  <div class="banner-preview"><img src="https://picsum.photos/120/80?random=11"></div>
+</template>
+
+<style scoped>
+.banner-preview {
+  height: 80px;
+  background: linear-gradient(135deg, #d3eafd, #91caff);
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #1d39c4;
+  font-size: 14px;
+}
+</style>

+ 15 - 0
src/components/decoration/BottomCtaPreview.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="cta-preview">底部按钮</div>
+</template>
+
+<style scoped>
+.cta-preview {
+  height: 48px;
+  background: #111;
+  border-radius: 24px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+}
+</style>

+ 15 - 0
src/components/decoration/BrandStoryPreview.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="brand-preview">品牌故事</div>
+</template>
+
+<style scoped>
+.brand-preview {
+  height: 80px;
+  background: #f9f0ff;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #722ed1;
+}
+</style>

+ 16 - 0
src/components/decoration/CouponCardPreview.vue

@@ -0,0 +1,16 @@
+<template>
+  <div class="coupon-preview">优惠券卡片</div>
+</template>
+
+<style scoped>
+.coupon-preview {
+  height: 70px;
+  background: #fff1f0;
+  border: 1px dashed #ff7875;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #cf1322;
+}
+</style>

+ 18 - 0
src/components/decoration/GoodsListPreview.vue

@@ -0,0 +1,18 @@
+<template>
+  <div class="goods-preview">
+    <div class="goods-item" v-for="i in 3" :key="i"></div>
+  </div>
+</template>
+
+<style scoped>
+.goods-preview {
+  display: flex;
+  gap: 6px;
+}
+.goods-item {
+  flex: 1;
+  height: 70px;
+  background: #f5f5f5;
+  border-radius: 6px;
+}
+</style>

+ 16 - 0
src/components/decoration/NoticeBarPreview.vue

@@ -0,0 +1,16 @@
+<template>
+  <div class="notice-preview">公告栏预览</div>
+</template>
+
+<style scoped>
+.notice-preview {
+  height: 40px;
+  background: #fff7e6;
+  border-radius: 20px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #d48806;
+  font-size: 13px;
+}
+</style>

+ 25 - 0
src/router/index.js

@@ -154,6 +154,12 @@ export const constantRoutes = [
         name: 'updateSopTemp',
         meta: { title: '改动SOP模板', activeMenu: '/qw/addSopTemp' }
       },
+        {
+            path: 'updateSopTempSXJZ/:id/:type(\\d+)', // 确保 :type 的正则匹配数字
+            component: () => import('@/views/qw/sopTemp/updateSopTempSXJZ'),
+            name: 'updateSopTempSXJZ',
+            meta: { title: '改动SOP模板', activeMenu: '/qw/addSopTemp' }
+        },
       {
         path: 'updateTemp/:id/:type(\\d+)', // 确保 :type 的正则匹配数字
         component: () => import('@/views/qw/sopTemp/updateTemp'),
@@ -419,6 +425,25 @@ export const constantRoutes = [
                 }
             }
         ]
+    },
+    {
+        path: '/decoration/template',
+        component: Layout,
+        children: [
+            {
+                path: 'list',
+                component: () => import('@/views/decoration/templateList'),
+                name: 'DecorationTemplateList',
+                meta: { title: '模板管理' }
+            },
+            {
+                path: 'editor',
+                component: () => import('@/views/decoration/templateEditor'),
+                name: 'DecorationTemplateEditor',
+                meta: { title: '模板编辑' },
+                hidden: true
+            }
+        ]
     }
 
 ]

+ 2 - 2
src/views/company/aiModel/account/info.vue

@@ -226,7 +226,7 @@ export default {
                 id: undefined,
                 name: '',
                 providerClassName: '',
-                concurrentNum: '',
+                concurrentNum: null,
                 interruptFlag: 0,
                 interruptKeywords: '',
                 interruptIgnoreKeywords: '呃 哦 哦哦 嗯 嗯嗯 嗯好的 好的 对 对对 是的 明白 啊 这样啊 是这样啊这样的 您好 你好',
@@ -344,7 +344,7 @@ export default {
             this.form.id = data.id
             this.form.name = data.name || ''
             this.form.providerClassName = data.providerClassName || ''
-            this.form.concurrentNum = data.concurrentNum || ''
+            this.form.concurrentNum = data.concurrentNum
             this.form.interruptFlag = data.interruptFlag || 0
             this.form.interruptKeywords = data.interruptKeywords || ''
             this.form.interruptIgnoreKeywords = data.interruptIgnoreKeywords || ''

+ 672 - 0
src/views/company/aiModel/inboundCallManage/inboundCallRecord.vue

@@ -0,0 +1,672 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索表单 -->
+    <el-form
+      v-show="showSearch"
+      ref="queryForm"
+      :inline="true"
+      :model="queryParams"
+      label-width="100px"
+    >
+      <el-form-item label="通话UUID" prop="uuid">
+        <el-input
+          v-model="queryParams.uuid"
+          clearable
+          placeholder="请输入通话UUID"
+          size="small"
+          style="width: 200px"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="主叫号码" prop="caller">
+        <el-input
+          v-model="queryParams.caller"
+          clearable
+          placeholder="请输入主叫号码"
+          size="small"
+          style="width: 200px"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="接听分机号" prop="extnum">
+        <el-input
+          v-model="queryParams.extnum"
+          clearable
+          placeholder="请输入接听分机号"
+          size="small"
+          style="width: 200px"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="通话时长(秒)">
+        <el-input-number
+          v-model="queryParams.params.timeLenStart"
+          :min="0"
+          size="small"
+          style="width: 120px"
+          placeholder="最小值"
+          controls-position="right"
+        />
+        <span class="range-separator">-</span>
+        <el-input-number
+          v-model="queryParams.params.timeLenEnd"
+          :min="0"
+          size="small"
+          style="width: 120px"
+          placeholder="最大值"
+          controls-position="right"
+        />
+      </el-form-item>
+      <el-form-item label="呼入时间">
+        <el-date-picker
+          v-model="inboundTimeRange"
+          type="datetimerange"
+          size="small"
+          style="width: 340px"
+          range-separator="至"
+          start-placeholder="开始时间"
+          end-placeholder="结束时间"
+          value-format="yyyy-MM-dd HH:mm:ss"
+        />
+      </el-form-item>
+      <el-form-item label="接听时间">
+        <el-date-picker
+          v-model="answeredTimeRange"
+          type="datetimerange"
+          size="small"
+          style="width: 340px"
+          range-separator="至"
+          start-placeholder="开始时间"
+          end-placeholder="结束时间"
+          value-format="yyyy-MM-dd HH:mm:ss"
+        />
+      </el-form-item>
+      <el-form-item label="挂机时间">
+        <el-date-picker
+          v-model="hangupTimeRange"
+          type="datetimerange"
+          size="small"
+          style="width: 340px"
+          range-separator="至"
+          start-placeholder="开始时间"
+          end-placeholder="结束时间"
+          value-format="yyyy-MM-dd HH:mm:ss"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button 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" />
+    </el-row>
+
+    <!-- 数据表格 -->
+    <el-table
+      v-loading="loading"
+      :data="tableData"
+      border
+      style="width: 100%"
+    >
+      <el-table-column align="center" label="UUID" prop="uuid" min-width="120" show-overflow-tooltip />
+      <el-table-column align="center" label="主叫号码" prop="caller" min-width="110" show-overflow-tooltip />
+       <el-table-column align="center" label="被叫号码" prop="callee" min-width="110" show-overflow-tooltip />
+      <el-table-column align="center" label="录音文件" min-width="80">
+        <template slot-scope="scope">
+          <el-tag v-if="getMediaType(scope.row.wavFile) === 'audio'" type="primary" size="small">音频</el-tag>
+          <el-tag v-else-if="getMediaType(scope.row.wavFile) === 'video'" type="success" size="small">视频</el-tag>
+          <span v-else>无</span>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="呼入时间" min-width="150">
+        <template slot-scope="scope">{{ formatTimestamp(scope.row.inboundTime) }}</template>
+      </el-table-column>
+      <el-table-column align="center" label="接听时间" min-width="150">
+        <template slot-scope="scope">{{ formatTimestamp(scope.row.answeredTime) }}</template>
+      </el-table-column>
+      <el-table-column align="center" label="接听分机" prop="extnum" min-width="90" />
+      <el-table-column align="center" label="接听坐席" prop="opnum" min-width="90" />
+      <el-table-column align="center" label="挂机时间" min-width="150">
+        <template slot-scope="scope">{{ formatTimestamp(scope.row.hangupTime) }}</template>
+      </el-table-column>
+      <el-table-column align="center" label="业务组" prop="groupName" min-width="100" show-overflow-tooltip />
+      <el-table-column align="center" label="通话时长" min-width="90">
+        <template slot-scope="scope">{{ formatDuration(scope.row.timeLen) }}</template>
+      </el-table-column>
+      <!-- <el-table-column align="center" label="挂机原因" min-width="120" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <span
+            class="hangup-cause-cell"
+            :title="scope.row.hangupCause"
+            @dblclick="copyText(formatHangupCause(scope.row.hangupCause))"
+          >{{ formatHangupCause(scope.row.hangupCause) }}</span>
+        </template>
+      </el-table-column> -->
+      <el-table-column
+        align="center"
+        class-name="small-padding fixed-width"
+        label="操作"
+        width="160"
+      >
+        <template slot-scope="scope">
+          <el-button
+            v-if="scope.row.wavFileUrl"
+            size="mini"
+            type="text"
+            icon="el-icon-video-play"
+            @click="handlePlay(scope.row)"
+          >播放</el-button>
+          <el-button
+            v-if="scope.row.wavFileUrl"
+            size="mini"
+            type="text"
+            icon="el-icon-download"
+            @click="handleDownload(scope.row)"
+          >下载</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页 -->
+    <pagination
+      v-show="total > 0"
+      :limit.sync="queryParams.pageSize"
+      :page.sync="queryParams.pageNum"
+      :total="total"
+      @pagination="getList"
+    />
+
+    <!-- 播放弹窗 -->
+    <el-dialog
+      title="录音播放"
+      :visible.sync="playDialogVisible"
+      width="700px"
+      append-to-body
+      @close="handlePlayDialogClose"
+    >
+      <!-- 音频/视频播放器 -->
+      <div v-if="currentMediaType === 'audio'" class="player-wrapper">
+        <audio
+          ref="audioPlayer"
+          :src="currentWavFileUrl"
+          controls
+          style="width: 100%"
+        />
+      </div>
+      <div v-else-if="currentMediaType === 'video'" class="player-wrapper">
+        <video
+          ref="videoPlayer"
+          :src="currentWavFileUrl"
+          controls
+          style="width: 100%"
+        />
+      </div>
+
+      <!-- AI对话内容 -->
+      <div v-if="chatDialogList.length > 0" class="chat-container">
+        <div class="chat-title">AI对话内容</div>
+        <div class="chat-list">
+          <div
+            v-for="(item, index) in chatDialogList"
+            :key="index"
+            class="dialog-item"
+            :class="item.role"
+          >
+            <!-- 左侧角色(assistant / agent / kb) -->
+            <template v-if="item.role !== 'user'">
+              <span class="role-icon">
+                <i :class="getRoleIcon(item.role)" />
+              </span>
+              <span class="role-label">{{ getRoleLabel(item.role) }}</span>
+              <div class="bubble">
+                <span class="content-text">
+                  {{ item.expanded ? item.content : getTruncatedContent(item.content) }}
+                  <span
+                    v-if="item.content.length > 200"
+                    class="more-text"
+                    @click="toggleExpand(index)"
+                  >{{ item.expanded ? '[收起]' : '[更多]' }}</span>
+                </span>
+                <el-tag v-if="item.isKb" size="mini" type="warning" class="kb-tag">知识库来源</el-tag>
+              </div>
+            </template>
+            <!-- 右侧角色(user) -->
+            <template v-else>
+              <div class="bubble">
+                <span class="content-text">
+                  {{ item.expanded ? item.content : getTruncatedContent(item.content) }}
+                  <span
+                    v-if="item.content.length > 200"
+                    class="more-text"
+                    @click="toggleExpand(index)"
+                  >{{ item.expanded ? '[收起]' : '[更多]' }}</span>
+                </span>
+              </div>
+              <span class="role-icon">
+                <i class="el-icon-user" />
+              </span>
+              <span class="role-label">客户</span>
+            </template>
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listInboundCdr } from '@/api/company/inboundCallManage'
+
+export default {
+  name: 'InboundCallRecord',
+  data() {
+    return {
+      // 遮罩层
+      loading: false,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 表格数据
+      tableData: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        uuid: undefined,
+        caller: undefined,
+        extnum: undefined,
+        params: {
+          timeLenStart: undefined,
+          timeLenEnd: undefined,
+          inboundTimeStart: undefined,
+          inboundTimeEnd: undefined,
+          answeredTimeStart: undefined,
+          answeredTimeEnd: undefined,
+          hangupTimeStart: undefined,
+          hangupTimeEnd: undefined
+        }
+      },
+      // 时间范围
+      inboundTimeRange: [],
+      answeredTimeRange: [],
+      hangupTimeRange: [],
+      // 播放弹窗
+      playDialogVisible: false,
+      currentWavFileUrl: '',
+      currentMediaType: '',
+      // AI对话列表
+      chatDialogList: []
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    /** 查询列表 */
+    getList() {
+      this.loading = true
+      // 处理时间范围参数(转为epoch毫秒时间戳,与后端Long类型字段匹配)
+      if (this.inboundTimeRange && this.inboundTimeRange.length === 2) {
+        this.queryParams.params.inboundTimeStart = new Date(this.inboundTimeRange[0]).getTime()
+        this.queryParams.params.inboundTimeEnd = new Date(this.inboundTimeRange[1]).getTime()
+      } else {
+        this.queryParams.params.inboundTimeStart = undefined
+        this.queryParams.params.inboundTimeEnd = undefined
+      }
+      if (this.answeredTimeRange && this.answeredTimeRange.length === 2) {
+        this.queryParams.params.answeredTimeStart = new Date(this.answeredTimeRange[0]).getTime()
+        this.queryParams.params.answeredTimeEnd = new Date(this.answeredTimeRange[1]).getTime()
+      } else {
+        this.queryParams.params.answeredTimeStart = undefined
+        this.queryParams.params.answeredTimeEnd = undefined
+      }
+      if (this.hangupTimeRange && this.hangupTimeRange.length === 2) {
+        this.queryParams.params.hangupTimeStart = new Date(this.hangupTimeRange[0]).getTime()
+        this.queryParams.params.hangupTimeEnd = new Date(this.hangupTimeRange[1]).getTime()
+      } else {
+        this.queryParams.params.hangupTimeStart = undefined
+        this.queryParams.params.hangupTimeEnd = undefined
+      }
+      // 通话时长:前端输入秒,数据库存毫秒,显示用Math.ceil(ms/1000)
+      // Math.ceil(v/1000)==N 等价于 (N-1)*1000 < v <= N*1000,即 v∈[(N-1)*1000+1, N*1000]
+      const params = { ...this.queryParams }
+      params.params = { ...this.queryParams.params }
+      if (params.params.timeLenStart != null && params.params.timeLenStart !== undefined) {
+        const n = params.params.timeLenStart
+        params.params.timeLenStart = n > 0 ? (n - 1) * 1000 + 1 : 0
+      }
+      if (params.params.timeLenEnd != null && params.params.timeLenEnd !== undefined) {
+        params.params.timeLenEnd = params.params.timeLenEnd * 1000
+      }
+      listInboundCdr(params).then(response => {
+        this.tableData = response.rows || []
+        this.total = response.total || 0
+        this.loading = false
+      }).catch(() => {
+        this.loading = false
+      })
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.inboundTimeRange = []
+      this.answeredTimeRange = []
+      this.hangupTimeRange = []
+      this.resetForm('queryForm')
+      this.queryParams = {
+        pageNum: 1,
+        pageSize: 10,
+        uuid: undefined,
+        caller: undefined,
+        extnum: undefined,
+        params: {
+          timeLenStart: undefined,
+          timeLenEnd: undefined,
+          inboundTimeStart: undefined,
+          inboundTimeEnd: undefined,
+          answeredTimeStart: undefined,
+          answeredTimeEnd: undefined,
+          hangupTimeStart: undefined,
+          hangupTimeEnd: undefined
+        }
+      }
+      this.handleQuery()
+    },
+    /** 格式化时间戳 - 毫秒级时间戳转 yyyy-MM-dd HH:mm:ss */
+    formatTimestamp(value) {
+      if (!value || value <= 0) return '-'
+      const date = new Date(Number(value))
+      const year = date.getFullYear()
+      const month = (date.getMonth() + 1).toString().padStart(2, '0')
+      const day = date.getDate().toString().padStart(2, '0')
+      const hour = date.getHours().toString().padStart(2, '0')
+      const minute = date.getMinutes().toString().padStart(2, '0')
+      const second = date.getSeconds().toString().padStart(2, '0')
+      return `${year}-${month}-${day} ${hour}:${minute}:${second}`
+    },
+    /** 格式化通话时长 - 毫秒转 mm分ss秒 */
+    formatDuration(value) {
+      if (!value || value <= 0) return '0秒'
+      const totalSeconds = Math.ceil(value / 1000)
+      const minutes = Math.floor(totalSeconds / 60)
+      const seconds = totalSeconds % 60
+      if (minutes > 0) {
+        return `${minutes}分${seconds.toString().padStart(2, '0')}秒`
+      }
+      return `${seconds}秒`
+    },
+    /** 判断媒体类型 */
+    getMediaType(wavFile) {
+      if (!wavFile) return 'none'
+      const ext = wavFile.split('.').pop().toLowerCase()
+      if (['wav', 'mp3', 'aac'].includes(ext)) return 'audio'
+      if (['mp4', 'avi', 'mov'].includes(ext)) return 'video'
+      return 'none'
+    },
+    /** 格式化挂机原因 */
+    formatHangupCause(value) {
+      if (!value) return '-'
+      try {
+        const obj = JSON.parse(value)
+        if (obj && obj.code !== undefined) {
+          return `${obj.code}: ${obj.details || ''}`
+        }
+        return value
+      } catch (e) {
+        return value
+      }
+    },
+    /** 双击复制文本 */
+    copyText(text) {
+      if (!text || text === '-') return
+      if (navigator.clipboard && window.isSecureContext) {
+        navigator.clipboard.writeText(text).then(() => {
+          this.$message.success('复制成功')
+        }).catch(() => {
+          this.fallbackCopy(text)
+        })
+      } else {
+        this.fallbackCopy(text)
+      }
+    },
+    /** 降级复制方案 */
+    fallbackCopy(text) {
+      const textArea = document.createElement('textarea')
+      textArea.value = text
+      textArea.style.position = 'fixed'
+      textArea.style.left = '-9999px'
+      document.body.appendChild(textArea)
+      textArea.focus()
+      textArea.select()
+      try {
+        document.execCommand('copy')
+        this.$message.success('复制成功')
+      } catch (err) {
+        this.$message.error('复制失败')
+      }
+      document.body.removeChild(textArea)
+    },
+    /** 播放按钮操作 */
+    handlePlay(row) {
+      this.currentWavFileUrl = row.wavFileUrl
+      this.currentMediaType = this.getMediaType(row.wavFileUrl || row.wavFile)
+      // 解析AI对话内容
+      this.chatDialogList = this.parseChatContent(row.chatContent)
+      this.playDialogVisible = true
+    },
+    /** 解析聊天内容 */
+    parseChatContent(chatContent) {
+      if (!chatContent) return []
+      try {
+        let items = chatContent
+        // 如果是字符串,尝试解析
+        if (typeof items === 'string') {
+          items = JSON.parse(items)
+        }
+        // 可能是双重JSON字符串
+        if (typeof items === 'string') {
+          items = JSON.parse(items)
+        }
+        if (!Array.isArray(items)) return []
+        return items.filter(item => ['user', 'assistant', 'agent'].includes(item.role)).map(item => {
+          let content = item.content || ''
+          let isKb = false
+          // 检查是否包含JSON(知识库返回结果)
+          if (this.containsJson(content)) {
+            isKb = true
+            // 移除JSON部分
+            let cleaned = content
+            while (this.containsJson(cleaned)) {
+              cleaned = cleaned.replace(/\{[^{}]*\}/s, '').trim()
+            }
+            content = cleaned || content
+          }
+          return {
+            role: isKb ? 'kb' : item.role,
+            content: content,
+            isKb: isKb,
+            expanded: false
+          }
+        }).filter(item => item.content)
+      } catch (e) {
+        return []
+      }
+    },
+    /** 检查字符串是否包含JSON */
+    containsJson(input) {
+      if (!input) return false
+      return /\{.*?\}/s.test(input)
+    },
+    /** 获取截断内容 */
+    getTruncatedContent(content) {
+      if (!content || content.length <= 200) return content
+      return content.substring(0, 200) + '...'
+    },
+    /** 切换展开/收起 */
+    toggleExpand(index) {
+      this.$set(this.chatDialogList[index], 'expanded', !this.chatDialogList[index].expanded)
+    },
+    /** 获取角色图标 */
+    getRoleIcon(role) {
+      const iconMap = {
+        assistant: 'el-icon-monitor',
+        agent: 'el-icon-phone-outline',
+        kb: 'el-icon-folder-opened',
+        user: 'el-icon-user'
+      }
+      return iconMap[role] || 'el-icon-user'
+    },
+    /** 获取角色标签 */
+    getRoleLabel(role) {
+      const labelMap = {
+        assistant: 'AI',
+        agent: '坐席',
+        kb: '知识库',
+        user: '客户'
+      }
+      return labelMap[role] || role
+    },
+    /** 下载按钮操作 */
+    handleDownload(row) {
+      if (row.wavFileUrl) {
+        const link = document.createElement('a')
+        link.href = row.wavFileUrl
+        link.target = '_blank'
+        link.download = ''
+        document.body.appendChild(link)
+        link.click()
+        document.body.removeChild(link)
+      }
+    },
+    /** 播放弹窗关闭 */
+    handlePlayDialogClose() {
+      // 停止音频/视频播放
+      if (this.$refs.audioPlayer) {
+        this.$refs.audioPlayer.pause()
+        this.$refs.audioPlayer.currentTime = 0
+      }
+      if (this.$refs.videoPlayer) {
+        this.$refs.videoPlayer.pause()
+        this.$refs.videoPlayer.currentTime = 0
+      }
+      this.currentWavFileUrl = ''
+      this.currentMediaType = ''
+      this.chatDialogList = []
+    }
+  }
+}
+</script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+}
+.range-separator {
+  padding: 0 5px;
+}
+.player-wrapper {
+  margin-bottom: 16px;
+}
+/* 对话容器 */
+.chat-container {
+  margin-top: 16px;
+  border-top: 1px solid #ebeef5;
+  padding-top: 12px;
+}
+.chat-title {
+  font-size: 15px;
+  font-weight: bold;
+  margin-bottom: 12px;
+  color: #303133;
+}
+.chat-list {
+  max-height: 400px;
+  overflow-y: auto;
+  padding-right: 6px;
+}
+/* 对话气泡 */
+.dialog-item {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 12px;
+}
+.dialog-item.user {
+  justify-content: flex-end;
+}
+.dialog-item.assistant,
+.dialog-item.agent,
+.dialog-item.kb {
+  justify-content: flex-start;
+}
+.bubble {
+  display: inline-block;
+  max-width: 70%;
+  padding: 10px 14px;
+  border-radius: 12px;
+  word-break: break-word;
+  line-height: 1.6;
+  font-size: 14px;
+  color: #263238;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
+}
+.dialog-item.user .bubble {
+  background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
+  text-align: right;
+}
+.dialog-item.assistant .bubble {
+  background: linear-gradient(135deg, #f5f5f5 0%, #eeeeee 100%);
+}
+.dialog-item.agent .bubble {
+  background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
+}
+.dialog-item.kb .bubble {
+  background: linear-gradient(135deg, #fff8e1 0%, #ffecb3 100%);
+}
+.role-icon {
+  display: flex;
+  align-items: center;
+  padding: 0 8px;
+  font-size: 18px;
+  color: #606266;
+}
+.role-label {
+  font-size: 12px;
+  color: #909399;
+  white-space: nowrap;
+  padding: 0 4px;
+  line-height: 32px;
+}
+.content-text {
+  word-break: break-word;
+}
+.more-text {
+  color: #1976d2;
+  cursor: pointer;
+  font-size: 13px;
+  margin-left: 4px;
+  white-space: nowrap;
+}
+.more-text:hover {
+  text-decoration: underline;
+}
+.kb-tag {
+  margin-top: 6px;
+  display: inline-block;
+}
+.hangup-cause-cell {
+  cursor: pointer;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 100%;
+  display: inline-block;
+}
+</style>

+ 709 - 0
src/views/company/aiModel/inboundCallManage/index.vue

@@ -0,0 +1,709 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索表单 -->
+    <el-form
+      v-show="showSearch"
+      ref="queryForm"
+      :inline="true"
+      :model="queryParams"
+      label-width="100px"
+    >
+      <el-form-item label="大模型底座" prop="llmAccountId">
+        <el-select
+          v-model="queryParams.llmAccountId"
+          clearable
+          placeholder="全部"
+          size="small"
+          style="width: 200px"
+        >
+          <el-option label="全部" value="" />
+          <el-option
+            v-for="item in llmAccountList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="被叫号码" prop="callee">
+        <el-input
+          v-model="queryParams.callee"
+          clearable
+          placeholder="请输入被叫号码"
+          size="small"
+          style="width: 200px"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button 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
+          v-hasPermi="['inboundCallManage:add']"
+          icon="el-icon-plus"
+          plain
+          size="mini"
+          type="success"
+          @click="handleAdd"
+        >新增</el-button>
+      </el-col>
+      <!-- <el-col :span="1.5">
+        <el-button
+          icon="el-icon-edit"
+          plain
+          size="mini"
+          type="primary"
+          :disabled="single"
+          @click="handleEdit()"
+        >修改</el-button>
+      </el-col> -->
+      <!-- <el-col :span="1.5">
+        <el-button
+          icon="el-icon-delete"
+          plain
+          size="mini"
+          type="danger"
+          :disabled="multiple"
+          @click="handleDelete()"
+        >删除</el-button>
+      </el-col> -->
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
+    </el-row>
+
+    <!-- 数据表格 -->
+    <el-table
+      v-loading="loading"
+      :data="inboundList"
+      border
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column align="center" label="名称" prop="inboundAlias" />
+      <el-table-column align="center" label="大模型底座" prop="llmAccountName" />
+      <el-table-column align="center" label="音色" prop="voiceName" />
+      <el-table-column align="center" label="被叫号码" prop="callee" />
+      <el-table-column align="center" label="服务类型" prop="serviceType" width="200">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.serviceType === 'ai'" type="primary">AI</el-tag>
+          <el-tag v-else-if="scope.row.serviceType === 'acd'" type="success">技能组</el-tag>
+          <el-tag v-else-if="scope.row.serviceType === 'ivr'" type="info">IVR</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column
+        align="center"
+        class-name="small-padding fixed-width"
+        label="操作"
+        width="180"
+      >
+        <template slot-scope="scope">
+          <el-button
+            v-hasPermi="['inboundCallManage:edit']"
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleEdit(scope.row)"
+          >修改</el-button>
+          <el-button
+          v-hasPermi="['inboundCallManage:delete']"
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页 -->
+    <pagination
+      v-show="total > 0"
+      :limit.sync="queryParams.pageSize"
+      :page.sync="queryParams.pageNum"
+      :total="total"
+      @pagination="getList"
+    />
+
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog
+      :title="dialogTitle"
+      :visible.sync="dialogVisible"
+      width="650px"
+      append-to-body
+      @close="handleDialogClose"
+    >
+      <el-form
+        ref="form"
+        :model="form"
+        :rules="rules"
+        label-width="120px"
+      >
+        <!-- 名称 -->
+        <el-form-item label="名称" prop="inboundAlias">
+          <el-input
+            v-model="form.inboundAlias"
+            placeholder="请输入名称"
+            style="width: 400px;"
+          />
+        </el-form-item>
+        
+        <!-- 被叫号码 -->
+        <el-form-item label="被叫号码" prop="callee">
+          <el-input
+            v-model="form.callee"
+            placeholder="请输入被叫号码"
+            style="width: 400px;"
+          />
+        </el-form-item>
+        
+        <!-- 服务类型 -->
+        <el-form-item label="服务类型" prop="serviceType">
+          <el-select
+            v-model="form.serviceType"
+            placeholder="请选择服务类型"
+            style="width: 400px;"
+            @change="handleServiceTypeChange"
+          >
+            <el-option label="AI" value="ai" />
+            <el-option label="技能组" value="acd" />
+            <el-option label="IVR" value="ivr" />
+          </el-select>
+        </el-form-item>
+        
+        <!-- 大模型底座 (AI可见) -->
+        <el-form-item v-if="form.serviceType === 'ai'" label="大模型底座" prop="llmAccountId">
+          <el-select
+            v-model="form.llmAccountId"
+            placeholder="请选择大模型底座"
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in llmAccountList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- ASR提供商 (AI可见) -->
+        <el-form-item v-if="form.serviceType === 'ai'" label="ASR提供商" prop="asrProvider">
+          <el-select
+            v-model="form.asrProvider"
+            placeholder="请选择ASR提供商"
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="(value, key) in asrProviderList"
+              :key="key"
+              :label="value"
+              :value="key"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- 音色来源 (AI可见) -->
+        <el-form-item v-if="form.serviceType === 'ai'" label="音色来源" prop="voiceSource">
+          <el-select
+            v-model="form.voiceSource"
+            placeholder="请选择音色来源"
+            style="width: 400px;"
+            @change="handleVoiceSourceChange"
+          >
+            <el-option
+              v-for="(value, key) in voiceSourceList"
+              :key="key"
+              :label="value"
+              :value="key"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- 音色 (AI可见) -->
+        <el-form-item v-if="form.serviceType === 'ai'" label="音色" prop="voiceCode">
+          <el-select
+            v-model="form.voiceCode"
+            placeholder="请选择音色"
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in voiceList"
+              :key="item.voiceCode"
+              :label="item.voiceName"
+              :value="item.voiceCode"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- AI转接类型 (AI可见) -->
+        <el-form-item v-if="form.serviceType === 'ai'" label="AI转接类型" prop="aiTransferType">
+          <el-select
+            v-model="form.aiTransferType"
+            placeholder="请选择AI转接类型"
+            style="width: 400px;"
+            @change="handleAiTransferTypeChange"
+          >
+            <el-option label="技能组" value="acd" />
+            <el-option label="分机号" value="extension" />
+            <el-option label="网关" value="gateway" />
+          </el-select>
+        </el-form-item>
+        
+        <!-- IVR下拉 (IVR可见) -->
+        <el-form-item v-if="form.serviceType === 'ivr'" label="IVR" prop="ivrId">
+          <el-select
+            v-model="form.ivrId"
+            placeholder="请选择IVR"
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in ivrList"
+              :key="item.id"
+              :label="item.ivrNodeName"
+              :value="String(item.id)"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- 业务组 (ACD/AI+ACD可见) -->
+        <el-form-item v-if="showBizGroup" label="业务组" prop="aiTransferGroupId">
+          <el-select
+            v-model="form.aiTransferGroupId"
+            placeholder="请选择业务组"
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in bizGroupList"
+              :key="item.groupId"
+              :label="item.bizGroupName"
+              :value="String(item.groupId)"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- 网关 (AI+Gateway可见) -->
+        <el-form-item v-if="showGateway" label="网关" prop="aiTransferGatewayId">
+          <el-select
+            v-model="form.aiTransferGatewayId"
+            placeholder="请选择网关"
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in gatewayList"
+              :key="item.id"
+              :label="item.gwDesc"
+              :value="String(item.id)"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- 网关目标号码 (AI+Gateway可见) -->
+        <el-form-item v-if="showGateway" label="网关目标号码" prop="aiTransferGatewayDestNumber">
+          <el-input
+            v-model="form.aiTransferGatewayDestNumber"
+            placeholder="请输入网关目标号码"
+            style="width: 400px;"
+          />
+        </el-form-item>
+        
+        <!-- 分机号 (AI+Extension可见) -->
+        <el-form-item v-if="showExtension" label="分机号" prop="aiTransferExtNumber">
+          <el-input
+            v-model="form.aiTransferExtNumber"
+            placeholder="请输入分机号"
+            style="width: 400px;"
+          />
+        </el-form-item>
+        
+        <!-- 服务评价 -->
+        <el-form-item label="服务评价" prop="satisfSurveyIvrId">
+          <el-select
+            v-model="form.satisfSurveyIvrId"
+            placeholder="请选择服务评价IVR"
+            clearable
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in ivrList"
+              :key="item.id"
+              :label="item.ivrNodeName"
+              :value="String(item.id)"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="呼入场景" prop="fsSceneType">
+          <el-select
+            v-model="form.fsSceneType"
+            placeholder="请选择呼入场景"
+            clearable
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in sceneList"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="parseInt(item.dictValue)"
+            />
+          </el-select>
+        </el-form-item>
+        <!-- <el-form-item label="线路回调地址" prop="callBackUrl">
+         <el-input
+            v-model="form.callBackUrl"
+            placeholder="请输入线路回调地址"
+            style="width: 400px;"
+          />
+        </el-form-item> -->
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  listInboundLlm,
+  addInboundLlm,
+  updateInboundLlm,
+  delInboundLlm,
+  listLlmAccount,
+  listAsrProvider,
+  listVoiceSource,
+  listVoiceBySource,
+  listBizGroup,
+  listGateway,
+  listIvr
+} from '@/api/company/inboundCallManage'
+
+export default {
+  name: 'InboundCallManage',
+  data() {
+    return {
+      // 遮罩层
+      loading: false,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 表格数据
+      inboundList: [],
+      // 选中的数据
+      selectedRows: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        llmAccountId: undefined,
+        callee: undefined
+      },
+      // 大模型账户列表
+      llmAccountList: [],
+      // ASR提供商列表
+      asrProviderList: {},
+      // 音色来源列表
+      voiceSourceList: {},
+      // 音色列表
+      voiceList: [],
+      // 业务组列表
+      bizGroupList: [],
+      // 网关列表
+      gatewayList: [],
+      // IVR列表
+      ivrList: [],
+      // 弹窗控制
+      dialogVisible: false,
+      dialogTitle: '新增呼入大模型配置',
+      // 表单数据
+      form: {
+        id: undefined,
+        inboundAlias: undefined,
+        callee: undefined,
+        serviceType: 'ai',
+        llmAccountId: undefined,
+        asrProvider: undefined,
+        voiceSource: undefined,
+        voiceCode: undefined,
+        aiTransferType: 'acd',
+        aiTransferGroupId: undefined,
+        aiTransferGatewayId: undefined,
+        aiTransferGatewayDestNumber: undefined,
+        aiTransferExtNumber: undefined,
+        ivrId: undefined,
+        satisfSurveyIvrId: undefined,
+        fsSceneType:null,
+        callBackUrl:null
+      },
+      // 表单校验规则
+      rules: {
+        inboundAlias: [
+          { required: true, message: '请输入名称', trigger: 'blur' }
+        ],
+        callee: [
+          { required: true, message: '请输入被叫号码', trigger: 'blur' }
+        ],
+        serviceType: [
+          { required: true, message: '请选择服务类型', trigger: 'change' }
+        ],
+        fsSceneType: [
+          { required: true, message: '请选择呼入场景', trigger: 'change' }
+        ],
+        callBackUrl: [
+          { required: true, message: '请输入线路回调地址', trigger: 'blur' }
+        ],
+      },
+      //场景下拉
+      sceneList:[]
+    }
+  },
+  computed: {
+    // 是否显示业务组选择
+    showBizGroup() {
+      return this.form.serviceType === 'acd' ||
+             (this.form.serviceType === 'ai' && this.form.aiTransferType === 'acd')
+    },
+    // 是否显示网关选择
+    showGateway() {
+      return this.form.serviceType === 'ai' && this.form.aiTransferType === 'gateway'
+    },
+    // 是否显示分机号输入
+    showExtension() {
+      return this.form.serviceType === 'ai' && this.form.aiTransferType === 'extension'
+    }
+  },
+  created() {
+    this.getList()
+    this.loadDropdownData()
+    this.getDicts("task_scene_type").then((response) => {
+        this.sceneList = response.data;
+        console.log(this.sceneList);
+    });
+  },
+  methods: {
+    /** 加载下拉数据 */
+    loadDropdownData() {
+      // 大模型账户
+      listLlmAccount().then(response => {
+        this.llmAccountList = response.data || []
+      })
+      // ASR提供商
+      listAsrProvider().then(response => {
+        this.asrProviderList = response.data || {}
+      })
+      // 音色来源
+      listVoiceSource().then(response => {
+        this.voiceSourceList = response.data || {}
+      })
+      // 业务组
+      listBizGroup().then(response => {
+        this.bizGroupList = response.data || []
+      })
+      // 网关
+      listGateway().then(response => {
+        this.gatewayList = response.data || []
+      })
+      // IVR
+      listIvr().then(response => {
+        this.ivrList = response.data || []
+      })
+    },
+    /** 查询列表 */
+    getList() {
+      this.loading = true
+      listInboundLlm(this.queryParams).then(response => {
+        this.inboundList = response.rows || []
+        this.total = response.total || 0
+        this.loading = false
+      }).catch(() => {
+        this.loading = false
+      })
+    },
+    /** 服务类型改变 */
+    handleServiceTypeChange() {
+      // 清空关联字段
+      this.form.llmAccountId = undefined
+      this.form.asrProvider = undefined
+      this.form.voiceSource = undefined
+      this.form.voiceCode = undefined
+      this.form.aiTransferType = 'acd'
+      this.form.ivrId = undefined
+      this.voiceList = []
+    },
+    /** 音色来源改变 */
+    handleVoiceSourceChange(val) {
+      this.form.voiceCode = undefined
+      if (val) {
+        listVoiceBySource(val).then(response => {
+          this.voiceList = response.data || []
+        })
+      } else {
+        this.voiceList = []
+      }
+    },
+    /** AI转接类型改变 */
+    handleAiTransferTypeChange() {
+      this.form.aiTransferGroupId = undefined
+      this.form.aiTransferGatewayId = undefined
+      this.form.aiTransferGatewayDestNumber = undefined
+      this.form.aiTransferExtNumber = undefined
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.handleQuery()
+    },
+    /** 表格多选选中事件 */
+    handleSelectionChange(selection) {
+      this.selectedRows = selection
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset()
+      this.dialogTitle = '新增呼入大模型配置'
+      this.dialogVisible = true
+    },
+    /** 修改按钮操作 */
+    handleEdit(row) {
+      this.reset()
+      let editRow = row
+      if (!editRow) {
+        if (this.selectedRows.length !== 1) {
+          this.$message.warning('请选择一条要修改的数据')
+          return
+        }
+        editRow = this.selectedRows[0]
+      }
+      this.dialogTitle = '修改呼入大模型配置'
+      this.form = { ...editRow }
+      // 如果有音色来源,加载音色列表
+      if (this.form.voiceSource) {
+        listVoiceBySource(this.form.voiceSource).then(response => {
+          this.voiceList = response.data || []
+        })
+      }
+      this.dialogVisible = true
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      let ids = []
+      if (row) {
+        ids = [row.id]
+      } else {
+        if (this.selectedRows.length === 0) {
+          this.$message.warning('请至少选择一条要删除的数据')
+          return
+        }
+        ids = this.selectedRows.map(item => item.id)
+      }
+      this.$confirm(`此操作将永久删除${ids.length > 1 ? '这些' : '该'}记录,是否继续?`, '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        delInboundLlm(ids.join(',')).then(response => {
+          if (response.code === 200) {
+            this.$message.success('删除成功')
+            this.getList()
+          } else {
+            this.$message.error(response.msg || '删除失败')
+          }
+        })
+      }).catch(() => {})
+    },
+    /** 表单重置 */
+    reset() {
+      this.form = {
+        id: undefined,
+        inboundAlias: undefined,
+        callee: undefined,
+        serviceType: 'ai',
+        llmAccountId: undefined,
+        asrProvider: undefined,
+        voiceSource: undefined,
+        voiceCode: undefined,
+        aiTransferType: 'acd',
+        aiTransferGroupId: undefined,
+        aiTransferGatewayId: undefined,
+        aiTransferGatewayDestNumber: undefined,
+        aiTransferExtNumber: undefined,
+        ivrId: undefined,
+        satisfSurveyIvrId: undefined
+      }
+      this.voiceList = []
+      this.resetForm('form')
+    },
+    /** 弹窗关闭时清理 */
+    handleDialogClose() {
+      this.reset()
+    },
+    /** 提交表单 */
+    submitForm() {
+      this.$refs.form.validate(valid => {
+        if (valid) {
+          // 处理转接数据
+          const submitData = { ...this.form }
+          if (submitData.serviceType === 'ai') {
+            if (submitData.aiTransferType === 'acd') {
+              submitData.aiTransferData = submitData.aiTransferGroupId
+            } else if (submitData.aiTransferType === 'extension') {
+              submitData.aiTransferData = submitData.aiTransferExtNumber
+            } else if (submitData.aiTransferType === 'gateway') {
+              submitData.aiTransferData = JSON.stringify({
+                gatewayId: submitData.aiTransferGatewayId,
+                destNumber: submitData.aiTransferGatewayDestNumber
+              })
+            }
+          } else if (submitData.serviceType === 'acd') {
+            submitData.aiTransferData = submitData.aiTransferGroupId
+          }
+          
+          if (submitData.id) {
+            updateInboundLlm(submitData).then(response => {
+              if (response.code === 200) {
+                this.$message.success('修改成功')
+                this.dialogVisible = false
+                this.getList()
+              } else {
+                this.$message.error(response.msg || '修改失败')
+              }
+            })
+          } else {
+            addInboundLlm(submitData).then(response => {
+              if (response.code === 200) {
+                this.$message.success('新增成功')
+                this.dialogVisible = false
+                this.getList()
+              } else {
+                this.$message.error(response.msg || '新增失败')
+              }
+            })
+          }
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+}
+</style>

+ 70 - 2
src/views/company/companyConfig/index.vue

@@ -42,13 +42,16 @@
                 <span class="tip-text">(到第几位结束)</span>
               </el-form-item>
             </template>
-            
+
              <el-form-item label="是否允许重复客户导入" prop="allowRepeatCustomer">
               <el-switch v-model="cidConfig.allowRepeatCustomer"></el-switch>
             </el-form-item>
-            <el-form-item label="回调地址" prop="callbackUrl">
+            <el-form-item label="Ai外呼回调地址" prop="callbackUrl">
                <el-input v-model="cidConfig.callbackUrl" style="width:800px"></el-input>
             </el-form-item>
+             <!-- <el-form-item label="线路呼入回调地址" prop="inboundCallbackUrl">
+               <el-input v-model="cidConfig.inboundCallbackUrl" style="width:800px"></el-input>
+            </el-form-item> -->
             <div class="line"></div>
             <div style="float:right;margin-right:20px">
               <el-button type="primary" @click="onSubmitCidConfig">提交</el-button>
@@ -236,6 +239,53 @@
             </div>
           </el-form>
         </el-tab-pane>
+        <el-tab-pane label="app提现配置" name="hisAppRedPacket" >
+              <el-form ref="hisAppRedPacket" :model="hisAppRedPacket"  label-width="150px">
+                  <el-form-item   label="红包接口类型" prop="isNew">
+                      <el-radio-group v-model="hisAppRedPacket.isNew">
+                          <el-radio label="0">商家转账到零钱(旧)</el-radio>
+                          <el-radio label="1">商家转账(新)</el-radio>
+                      </el-radio-group>
+                  </el-form-item>
+                  <el-form-item   label="公众号appid" prop="appId">
+                      <el-input   v-model="hisAppRedPacket.appId"  label="请输入appId"></el-input>
+                  </el-form-item>
+                  <el-form-item   label="小程序appid" prop="appId">
+                      <el-input   v-model="hisAppRedPacket.miniappId"  label="请输入appId"></el-input>
+                  </el-form-item>
+                  <el-form-item   label="商户号" prop="mchId">
+                      <el-input   v-model="hisAppRedPacket.mchId"  label="请输入mchId"></el-input>
+                  </el-form-item>
+                  <el-form-item   label="商户密钥" prop="mchKey">
+                      <el-input   v-model="hisAppRedPacket.mchKey"  label="mchKey"></el-input>
+                  </el-form-item>
+                  <el-form-item   label="p12证书路径" prop="keyPath">
+                      <el-input   v-model="hisAppRedPacket.keyPath"  label="请输入keyPath"></el-input>
+                  </el-form-item>
+                  <el-form-item   label="apiV3密钥" prop="apiV3Key">
+                      <el-input   v-model="hisAppRedPacket.apiV3Key"  label="请输入apiV3Key"></el-input>
+                  </el-form-item>
+                  <el-form-item   label="公钥ID" prop="publicKeyId">
+                      <el-input   v-model="hisAppRedPacket.publicKeyId"  label="请输入公钥ID"></el-input>
+                  </el-form-item>
+                  <el-form-item   label="公钥证书" prop="publicKeyPath">
+                      <el-input   v-model="hisAppRedPacket.publicKeyPath"  label="请输入publicKeyPath"></el-input>
+                  </el-form-item>
+                  <el-form-item   label="key路径" prop="privateKeyPath">
+                      <el-input   v-model="hisAppRedPacket.privateKeyPath"  label="请输入"></el-input>
+                  </el-form-item>
+                  <el-form-item   label="cert路径" prop="privateCertPath">
+                      <el-input   v-model="hisAppRedPacket.privateCertPath"  label="请输入"></el-input>
+                  </el-form-item>
+                  <el-form-item   label="回调地址" prop="notifyUrl">
+                      <el-input   v-model="hisAppRedPacket.notifyUrl"  label="请输入"></el-input>
+                  </el-form-item>
+
+                  <div style="float:right;margin-right:20px">
+                      <el-button type="primary" @click="onSubmit8">提交</el-button>
+                  </div>
+              </el-form>
+          </el-tab-pane>
         <el-tab-pane label="配置销售会员审核" name="companyUserConfig">
           <el-form ref="companyUserConfig" label-width="140px">
             <el-form-item label="会员是否小黑屋"><!--会员是否默认黑名单-->
@@ -366,8 +416,10 @@ export default {
       adminIsShowForm:{},
       userIsDefaultBlack: null,
       redPacketConfig:{},
+      hisAppRedPacket:{},
 
       redPacketConfigForm:{},
+      hisAppRedPacketForm:{},
       cidConfigForm:{}
     };
   },
@@ -532,6 +584,12 @@ export default {
                 this.cidConfig = JSON.parse(response.data.configValue);
               }
             }
+            else if(key == "his:AppRedPacket"){
+                this.hisAppRedPacketForm = response.data;
+                if(response.data.configValue != null){
+                    this.hisAppRedPacket = JSON.parse(response.data.configValue);
+                }
+            }
         });
     },
     onSubmit1() {
@@ -607,6 +665,16 @@ export default {
         }
       })
     },
+
+      onSubmit8() {
+          this.hisAppRedPacketForm.configValue=JSON.stringify(this.hisAppRedPacket);
+          updateConfig(this.hisAppRedPacketForm).then(response => {
+              if (response.code === 200) {
+                  this.msgSuccess("修改成功");
+                  this.getConfigKey("his:AppRedPacket");
+              }
+          });
+      },
     onSubmitCidConfig() {
       if (this.cidConfig.enablePhoneConfig) {
         if (!this.cidConfig.generateCount || this.cidConfig.generateCount < 1) {

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

@@ -120,6 +120,8 @@
             <el-tag v-if="scope.row.taskStatus == 3" type="success">执行完成</el-tag>
         </template>
       </el-table-column>
+        <el-table-column label="创建人" align="center" prop="createByName"/>
+        <el-table-column label="创建部门" align="center" prop="createByDeptName"/>
 <!--      <el-table-column label="外呼状态" align="center">-->
 <!--        <template slot-scope="scope">-->
 <!--          <div v-loading="loadingStatus">-->
@@ -167,6 +169,13 @@
             @click="startRoboticFun(scope.row.taskId)"
             v-hasPermi="['system:companyVoiceRobotic:list']"
           >开启外呼任务</el-button> -->
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.taskStatus == 1 && 1==2"
+            @click="pauseRoboticFun(scope.row.taskId)"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
+          >暂停任务</el-button>
           <el-button
             size="mini"
             type="text"
@@ -479,6 +488,45 @@
             {{ scope.row.roboticCallOutCount == null ? 0 : scope.row.roboticCallOutCount }}
           </template>
         </el-table-column>
+          <el-table-column label="AI标签" align="right" prop="customerId" width="250">
+              <template slot-scope="scope">
+                  <div v-if="scope.row.tagList && scope.row.tagList.length" class="ai-tags-container">
+                      <div v-for="tag in scope.row.tagList" :key="tag.id" class="ai-tag-item">
+                          <div class="tag-main-content">
+                              <span class="tag-property-name">{{ tag.propertyName }}</span>
+                              <span class="tag-property-value">
+                                  <span v-if="Array.isArray(tag.propertyValue)">{{ tag.propertyValue.join('、') }}</span>
+                                  <span v-else>{{ tag.propertyValue }}</span>
+                              </span>
+                          </div>
+                          <div v-if="tag.intention || (tag.likeRatio !== null && tag.likeRatio !== undefined)" class="tag-meta-info">
+                              <el-tag v-if="tag.intention" size="mini" effect="plain" class="meta-tag intention-tag">
+                                  <i class="el-icon-star-on"></i> {{ getIntentionText(tag.intention) }}
+                              </el-tag>
+                              <el-tag v-if="tag.likeRatio !== null && tag.likeRatio !== undefined" size="mini" type="success" effect="plain" class="meta-tag ratio-tag">
+                                  <i class="el-icon-trend-chart"></i> {{ tag.likeRatio }}%
+                              </el-tag>
+                          </div>
+                      </div>
+                  </div>
+                  <span v-else class="no-tags">暂无标签</span>
+              </template>
+          </el-table-column>
+          <el-table-column label="是否添加客服" align="center" prop="isAdd">
+              <template slot-scope="scope">
+                  <el-tag
+                      :type="scope.row.isAdd === 1 ? 'success' : 'info'"
+                      :style="{
+                  backgroundColor: scope.row.isAdd === 1 ? '#67C23A' : '#909399',
+                  color: '#fff',
+                  border: 'none'
+                }"
+                  >
+                      {{ scope.row.isAdd === 1 ? '已添加' : '未添加' }}
+                  </el-tag>
+              </template>
+          </el-table-column>
+          <el-table-column label="客服ID" align="center" prop="customerId"/>
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
           <template slot-scope="scope">
             <el-button
@@ -865,10 +913,11 @@ import {getDicts} from "@/api/system/dict/data";
 import { optionList } from '@/api/company/companyWorkflow'
 import {wxListQw} from "../../../api/company/companyVoiceRobotic";
 import CallCenterPhoneBar from '../../aiSipCall/aiSipCallManualOutbound.vue'
+import AiTagPanel from "../../crm/components/AiTagPanel.vue";
 
 export default {
   name: "Robotic",
-  components: { draggable, customerDetails, customerSelect, qwUserSelect,qwUserSelectTwo,CallCenterPhoneBar},
+  components: {AiTagPanel, draggable, customerDetails, customerSelect, qwUserSelect,qwUserSelectTwo,CallCenterPhoneBar},
   data() {
     return {
       taskType:1,
@@ -1121,6 +1170,17 @@ export default {
     }
   },
   methods: {
+
+      getIntentionText(intention) {
+          const intentionMap = {
+              high: "高意向",
+              medium: "中意向",
+              low: "低意向",
+              none: "无意向"
+          };
+          return intentionMap[intention] || intention;
+      },
+
     getSmsTempDropList(){
       getSmsTempList().then(res=>{
         this.smsTempList = res.data;
@@ -1357,6 +1417,7 @@ export default {
     getCalleesList() {
       this.callees.loading = true;
       calleesList(this.callees.queryParams).then(response => {
+          console.log("response",response);
         this.callees.list = response.rows;
         this.callees.total = response.total;
         this.callees.loading = false;
@@ -1703,6 +1764,10 @@ export default {
       const item = (this.levelList || []).find(e => String(e.dictValue) === String(val))
       return item ? item.dictLabel : val
     },
+    //暂停任务
+    pauseRoboticFun(taskId){
+
+    }
   }
 };
 </script>
@@ -2159,4 +2224,114 @@ export default {
     font-size: 14px;
     color: #606266;
 }
+
+/* AI标签样式优化 */
+.ai-tags-container {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+    padding: 4px 0;
+    max-height: 270px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    scroll-behavior: smooth;
+}
+
+.ai-tags-container::-webkit-scrollbar {
+    width: 6px;
+}
+
+.ai-tags-container::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 3px;
+}
+
+.ai-tags-container::-webkit-scrollbar-thumb {
+    background: #c0c4cc;
+    border-radius: 3px;
+    transition: background 0.3s ease;
+}
+
+.ai-tags-container::-webkit-scrollbar-thumb:hover {
+    background: #909399;
+}
+
+.ai-tag-item {
+    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    padding: 10px 12px;
+    transition: all 0.3s ease;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
+    flex-shrink: 0;
+}
+
+.ai-tag-item:hover {
+    border-color: #c0c4cc;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+    transform: translateY(-1px);
+}
+
+.tag-main-content {
+    display: flex;
+    align-items: baseline;
+    gap: 8px;
+    margin-bottom: 6px;
+}
+
+.tag-property-name {
+    font-size: 13px;
+    color: #909399;
+    font-weight: 500;
+    white-space: nowrap;
+    flex-shrink: 0;
+}
+
+.tag-property-value {
+    font-size: 13px;
+    color: #303133;
+    font-weight: 600;
+    flex: 1;
+    word-break: break-all;
+}
+
+.tag-meta-info {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding-top: 6px;
+    border-top: 1px dashed #e4e7ed;
+}
+
+.meta-tag {
+    font-size: 12px;
+    padding: 0 8px;
+    height: 22px;
+    line-height: 20px;
+    border-radius: 4px;
+    font-weight: 500;
+}
+
+.meta-tag i {
+    margin-right: 3px;
+    font-size: 12px;
+}
+
+.intention-tag {
+    background: linear-gradient(135deg, #fff1f0 0%, #ffffff 100%);
+    border-color: #ffa39e;
+    color: #cf1322;
+}
+
+.ratio-tag {
+    background: linear-gradient(135deg, #f6ffed 0%, #ffffff 100%);
+    border-color: #b7eb8f;
+    color: #389e0d;
+}
+
+.no-tags {
+    color: #c0c4cc;
+    font-size: 13px;
+    font-style: italic;
+}
 </style>

+ 2308 - 0
src/views/company/companyVoiceRobotic/myIndex.vue

@@ -0,0 +1,2308 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="任务名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入任务名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <!-- <el-form-item label="机器人" prop="robot">
+        <el-select v-model="queryParams.robot" filterable clearable>
+          <el-option v-for="item in robotList" :label="item.name + '('+item.num+')'" :value="item.id"/>
+        </el-select>
+      </el-form-item> -->
+      <el-form-item label="任务类型" prop="taskType">
+        <el-select v-model="queryParams.taskType" filterable clearable>
+          <el-option v-for="item in taskTypeList" :key="item.id" :label="item.name" :value="item.id"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['system:companyVoiceRobotic:add']"
+        >新增
+        </el-button>
+      </el-col>
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="danger"-->
+<!--          icon="el-icon-delete"-->
+<!--          size="mini"-->
+<!--          :disabled="multiple"-->
+<!--          @click="handleDelete"-->
+<!--          v-hasPermi="['system:companyVoiceRobotic:remove']"-->
+<!--        >删除-->
+<!--        </el-button>-->
+<!--      </el-col>-->
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="warning"-->
+<!--          icon="el-icon-download"-->
+<!--          size="mini"-->
+<!--          @click="handleExport"-->
+<!--          v-hasPermi="['system:companyVoiceRobotic:export']"-->
+<!--        >导出-->
+<!--        </el-button>-->
+<!--      </el-col>-->
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="success"-->
+<!--          icon="el-icon-refresh"-->
+<!--          size="mini"-->
+<!--          @click="updateStatusFun"-->
+<!--          v-hasPermi="['system:companyVoiceRobotic:list']"-->
+<!--        >更新状态-->
+<!--        </el-button>-->
+<!--      </el-col>-->
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="roboticList">
+      <el-table-column label="ID" align="center" prop="id"/>
+      <el-table-column label="任务名称" align="center" prop="name"/>
+      <el-table-column label="任务流程" align="center" prop="name">
+        <template slot-scope="scope">
+          <el-tag v-for="item in workflowList" v-if="scope.row.companyAiWorkflowId == item.value">{{item.label}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="加微方式" align="center" prop="dialogId">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.addType == 0">平均</el-tag>
+          <el-tag v-if="scope.row.addType == 1">意向</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="添加类型" align="center" prop="isWeCom">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.isWeCom == 1">个微</el-tag>
+          <el-tag v-if="scope.row.isWeCom == 2">企微</el-tag>
+        </template>
+      </el-table-column>
+       <el-table-column label="任务类型" align="center" prop="taskType">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.taskType == 1">普通任务</el-tag>
+          <el-tag v-if="scope.row.taskType == 2">场景任务 - {{scope.row.sceneTypeName}}</el-tag>
+        </template>
+      </el-table-column>
+       <el-table-column label="生效时间段" align="center" prop="taskType" width="250">
+        <template slot-scope="scope">
+          <div v-if="scope.row.taskType == 1">
+            {{scope.row.runtimeRangeStart}} ~ {{scope.row.runtimeRangeEnd}}
+          </div>
+          <div v-if="scope.row.taskType == 2" >
+            <div v-if="!!scope.row.availableStartTime && !!scope.row.availableEndTime ">
+            适用:{{scope.row.availableStartTime}} ~ {{scope.row.availableEndTime}}
+            </div>
+            <div v-if="!!scope.row.runtimeRangeStart && !!scope.row.runtimeRangeEnd">
+            运行:{{scope.row.runtimeRangeStart}} ~ {{scope.row.runtimeRangeEnd}}
+            </div>
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column label="任务状态" align="center">
+        <template slot-scope="scope">
+            <el-tag v-if="scope.row.taskStatus == 0">未启动</el-tag>
+            <el-tag v-if="scope.row.taskStatus == 1" type="warning">执行中</el-tag>
+            <el-tag v-if="scope.row.taskStatus == 2" type="danger">执行中断</el-tag>
+            <el-tag v-if="scope.row.taskStatus == 3" type="success">执行完成</el-tag>
+        </template>
+      </el-table-column>
+
+        <el-table-column label="创建人" align="center" prop="createByName"/>
+        <el-table-column label="创建部门" align="center" prop="createByDeptName"/>
+<!--      <el-table-column label="外呼状态" align="center">-->
+<!--        <template slot-scope="scope">-->
+<!--          <div v-loading="loadingStatus">-->
+<!--            <p v-if="statusObj.hasOwnProperty(scope.row.taskId)">-->
+<!--              <el-tag v-if="statusObj[scope.row.taskId].runningStatus == 0">未启动</el-tag>-->
+<!--              <el-tag v-if="statusObj[scope.row.taskId].runningStatus == 1">运行中</el-tag>-->
+<!--              <el-tag v-if="statusObj[scope.row.taskId].runningStatus == 2">已暂停</el-tag>-->
+<!--              <el-tag v-if="statusObj[scope.row.taskId].runningStatus == 3">已停止</el-tag>-->
+<!--            </p>-->
+<!--            <p v-if="!statusObj.hasOwnProperty(scope.row.taskId)"><el-tag>空</el-tag></p>-->
+<!--          </div>-->
+<!--        </template>-->
+<!--      </el-table-column>-->
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            @click="calleesOpen(scope.row.id)"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
+          >客户列表</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="wxOpen(scope.row.id,scope.row.isWeCom)"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
+          >加微管理</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-document"
+            @click="showExecLogs(scope.row)"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
+          >执行日志</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.taskStatus == 0"
+            @click="taskRunFun(scope.row.id)"
+          >启动任务</el-button>
+          <!-- <el-button
+            size="mini"
+            type="text"
+            v-if="statusObj.hasOwnProperty(scope.row.taskId) && (statusObj[scope.row.taskId].runningStatus == 0 || statusObj[scope.row.taskId].runningStatus == 3)"
+            @click="startRoboticFun(scope.row.taskId)"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
+          >开启外呼任务</el-button> -->
+          <el-button
+            size="mini"
+            type="text"
+            v-if="statusObj.hasOwnProperty(scope.row.taskId) && (statusObj[scope.row.taskId].runningStatus == 1 || statusObj[scope.row.taskId].runningStatus == 2)"
+            @click="stopRoboticFun(scope.row.taskId)"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
+          >停止任务</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['system:companyVoiceRobotic: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-drawer size="45%" :title="title" :visible.sync="open" width="500px" append-to-body class="task-form-drawer">
+      <div class="drawer-content">
+        <el-form ref="form" :model="form" :rules="rules" label-width="90px" class="task-form">
+            <div class="form-section" >
+               <div class="section-title">
+                <i class="el-icon-document"></i>
+                <span>任务类型</span>
+              </div>
+             <el-form-item label="任务类型" prop="taskType">
+              <el-select v-model="form.taskType" filterable placeholder="请选择任务类型" @change="taskTypeChange()">
+                <el-option v-for="item in taskTypeList" :key="item.id" :label="item.name" :value="item.id"/>
+              </el-select>
+            </el-form-item>
+             </div>
+
+          <div class="form-section" v-if="form.taskType === 1" >
+            <div class="section-title">
+              <i class="el-icon-document"></i>
+              <span>基本信息</span>
+            </div>
+            <el-form-item label="任务名称" prop="name">
+              <el-input v-model="form.name" placeholder="请输入任务名称" clearable/>
+            </el-form-item>
+            <el-form-item label="拨打客户" prop="userIds">
+              <el-button icon="el-icon-user" @click="openSelect">
+                选择客户
+                <el-tag v-if="form.userIds && form.userIds.length" type="primary" size="small" style="margin-left: 8px;">
+                  {{ form.userIds.length }}
+                </el-tag>
+              </el-button>
+            </el-form-item>
+            <el-form-item label="任务流程" prop="companyAiWorkflowId">
+              <el-select v-model="form.companyAiWorkflowId" filterable placeholder="请选择任务流程">
+                <el-option v-for="item in workflowList" :key="item.value" :label="item.label" :value="item.value"/>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="运行时间" required>
+                <el-col :span="11">
+                  <el-form-item prop="runtimeRangeStart">
+                     <el-time-select
+                  placeholder="任务运行开始时间"
+                   style="width: 100%;"
+                  v-model="form.runtimeRangeStart"
+                  key="runtimeRangeStart"
+                  :picker-options="{
+                    start: '07:00',
+                    step: '00:30',
+                    end: '22:00'
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+                <el-col class="line" :span="2" style="text-align: center">-</el-col>
+                <el-col :span="11">
+                  <el-form-item prop="runtimeRangeEnd">
+                     <el-time-select
+                      style="width: 100%;"
+                  placeholder="任务运行结束时间"
+                  v-model="form.runtimeRangeEnd"
+                  key="runtimeRangeEnd"
+                  :picker-options="{
+                    start: '07:00',
+                    step: '00:30',
+                    end: '22:00',
+                    minTime: form.runtimeRangeStart
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+              </el-form-item>
+          </div>
+          <!-- 场景任务 -->
+          <div class="form-section" v-if="form.taskType === 2" >
+            <div class="section-title">
+              <i class="el-icon-document"></i>
+              <span>基本信息</span>
+            </div>
+            <el-form-item label="任务名称" prop="name">
+              <el-input v-model="form.name" placeholder="请输入任务名称" clearable/>
+            </el-form-item>
+            <el-form-item label="任务流程" prop="companyAiWorkflowId">
+              <el-select v-model="form.companyAiWorkflowId" filterable placeholder="请选择任务流程">
+                <el-option v-for="item in workflowList" :key="item.value" :label="item.label" :value="item.value"/>
+              </el-select>
+            </el-form-item>
+             <el-form-item label="场景类型" prop="sceneType">
+              <el-select v-model="form.sceneType" filterable placeholder="请选择场景类型">
+                <el-option v-for="opt in sceneList" :key="opt.dictValue" :label="opt.dictLabel" :value="opt.dictValue"/>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="运行时间" required>
+                <el-col :span="11">
+                  <el-form-item prop="runtimeRangeStart">
+                     <el-time-select
+                  placeholder="任务运行开始时间"
+                   style="width: 100%;"
+                  v-model="form.runtimeRangeStart"
+                  key="runtimeRangeStart"
+                  :picker-options="{
+                    start: '07:00',
+                    step: '00:30',
+                    end: '22:00'
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+                <el-col class="line" :span="2" style="text-align: center">-</el-col>
+                <el-col :span="11">
+                  <el-form-item prop="runtimeRangeEnd">
+                     <el-time-select
+                      style="width: 100%;"
+                  placeholder="任务运行结束时间"
+                  v-model="form.runtimeRangeEnd"
+                  key="runtimeRangeEnd"
+                  :picker-options="{
+                    start: '07:00',
+                    step: '00:30',
+                    end: '22:00',
+                    minTime: form.runtimeRangeStart
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+              </el-form-item>
+            <el-form-item label="适用时间" required>
+                <el-col :span="11">
+                  <el-form-item prop="availableStartTime">
+                     <el-time-select
+                  placeholder="场景适用开始时间"
+                   style="width: 100%;"
+                  v-model="form.availableStartTime"
+                  key="availableStartTime"
+                  :picker-options="{
+                    start: '00:00',
+                    step: '00:30',
+                    end: '23:30'
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+                <el-col class="line" :span="2" style="text-align: center">-</el-col>
+                <el-col :span="11">
+                  <el-form-item prop="availableEndTime">
+                   <el-time-select
+                  style="width: 100%;"
+                  placeholder="场景适用结束时间"
+                  v-model="form.availableEndTime"
+                  key="availableEndTime"
+                  :picker-options="{
+                    start: '00:00',
+                    step: '00:30',
+                    end: '23:30'
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+              </el-form-item>
+
+          </div>
+
+          <div class="form-section">
+            <div class="section-title">
+              <i class="el-icon-setting"></i>
+              <span>加微设置</span>
+            </div>
+            <el-form-item label="加微方式" prop="addType">
+              <el-radio-group v-model="form.addType">
+                <el-radio :label="0" border>
+                  <i class="el-icon-pie-chart"></i>
+                  平均分配
+                </el-radio>
+                <el-radio :label="1" border>
+                  <i class="el-icon-star-on"></i>
+                  意向分配
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </div>
+
+          <div class="form-section account-section">
+            <div class="section-title">
+              <i class="el-icon-user-solid"></i>
+              <span>分配账号</span>
+            </div>
+            <el-form-item label="添加类型" prop="isWeCom">
+              <el-radio-group v-model="form.isWeCom">
+                <el-radio :label="1" border>
+                  <i class="el-icon-pie-chart"></i>
+                  个微
+                </el-radio>
+                <el-radio :label="2" border>
+                  <i class="el-icon-star-on"></i>
+                  企微
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-button type="primary" size="mini" icon="el-icon-plus" @click="addQwUser" plain>添加</el-button>
+
+            <div class="account-list" v-if="form.qwUser && form.qwUser.length">
+              <div v-for="(item, index) in form.qwUser" :key="index" class="account-item">
+                <el-row :gutter="12">
+                  <el-col :span="form.addType == 1 ? 6 : 0" v-if="form.addType == 1">
+                    <el-select v-model="item.intention" placeholder="意向等级" filterable clearable size="small">
+                      <el-option v-for="opt in levelList" :key="opt.dictValue" :label="opt.dictLabel" :value="opt.dictValue"/>
+                    </el-select>
+                  </el-col>
+                  <el-col :span="form.addType == 1 ? 7 : 10">
+                    <el-button icon="el-icon-user" @click="openQwUserSelect(index)" size="small" style="width: 100%;">
+                      选择账号
+                      <el-tag v-if="item.companyUserId && item.companyUserId.length" type="success" size="mini" style="margin-left: 6px;">
+                        {{ item.companyUserId.length }}
+                      </el-tag>
+                    </el-button>
+                  </el-col>
+                  <!-- <el-col :span="form.addType == 1 ? 9 : 12">
+                    <el-select v-model="item.wxDialogId" placeholder="选择话术" filterable size="small">
+                      <el-option v-for="dialog in wxDialogList" :key="dialog.id" :label="dialog.name" :value="dialog.id"/>
+                    </el-select>
+                  </el-col> -->
+                  <el-col :span="2">
+                    <el-button type="danger" icon="el-icon-delete" circle @click="removeQwUser(index)" size="small"></el-button>
+                  </el-col>
+                </el-row>
+              </div>
+            </div>
+            <el-empty v-else description="暂无分配账号" :image-size="80"></el-empty>
+          </div>
+          <!-- <el-form-item label="模式" prop="mode">
+            <el-select v-model="form.mode">
+              <el-option label="呼叫机器人后挂断" :value="7"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="呼叫倍率" prop="multiplier">
+            <el-select v-model="form.multiplier">
+              <el-option :value="1"/>
+              <el-option :value="2"/>
+              <el-option :value="3"/>
+              <el-option :value="4"/>
+              <el-option :value="5"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="自动重呼" prop="autoRecall">
+            <el-radio v-model="form.autoRecall" :label="0">否</el-radio>
+            <el-radio v-model="form.autoRecall" :label="1">是</el-radio>
+          </el-form-item>
+          <el-form-item label="重呼次数" prop="recallTimes" v-if="form.autoRecall == 1">
+            <el-select v-model="form.recallTimes">
+              <el-option label="不自动重呼" :value="0"/>
+              <el-option :value="1"/>
+              <el-option :value="2"/>
+              <el-option :value="3"/>
+              <el-option :value="4"/>
+              <el-option :value="5"/>
+            </el-select>
+          </el-form-item> -->
+        </el-form>
+        <div slot="footer" class="dialog-footer" style="text-align:right">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </div>
+    </el-drawer>
+    <customer-select @success="selectFun" ref="customer"/>
+    <component
+      :is="getCurrentComponent()"
+      @success="selectQwUserFun"
+      ref="dynamicQwUserSelect"
+    />
+
+    <el-drawer title="呼叫客户列表" size="60%" :visible.sync="callees.show" width="800px" append-to-body>
+      <el-table v-loading="callees.loading" :data="callees.list">
+        <el-table-column label="电话号码" align="center" prop="phone"/>
+        <el-table-column label="客户名称" align="center" prop="userName"/>
+        <el-table-column label="客户ID" align="center" prop="userId"/>
+        <el-table-column label="客户意向度" align="center">
+          <template slot-scope="scope">
+            <el-tag v-for="item in levelList" v-if="scope.row.intention == item.dictValue">{{item.dictLabel}}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="呼出次数" align="right" prop="roboticCallOutCount" width="100">
+          <template slot-scope="scope">
+            {{ scope.row.roboticCallOutCount == null ? 0 : scope.row.roboticCallOutCount }}
+          </template>
+        </el-table-column>
+          <el-table-column label="AI标签" align="right" prop="customerId" width="250">
+              <template slot-scope="scope">
+                  <div v-if="scope.row.tagList && scope.row.tagList.length" class="ai-tags-container">
+                      <div v-for="tag in scope.row.tagList" :key="tag.id" class="ai-tag-item">
+                          <div class="tag-main-content">
+                              <span class="tag-property-name">{{ tag.propertyName }}</span>
+                              <span class="tag-property-value">
+                                  <span v-if="Array.isArray(tag.propertyValue)">{{ tag.propertyValue.join('、') }}</span>
+                                  <span v-else>{{ tag.propertyValue }}</span>
+                              </span>
+                          </div>
+                          <div v-if="tag.intention || (tag.likeRatio !== null && tag.likeRatio !== undefined)" class="tag-meta-info">
+                              <el-tag v-if="tag.intention" size="mini" effect="plain" class="meta-tag intention-tag">
+                                  <i class="el-icon-star-on"></i> {{ getIntentionText(tag.intention) }}
+                              </el-tag>
+                              <el-tag v-if="tag.likeRatio !== null && tag.likeRatio !== undefined" size="mini" type="success" effect="plain" class="meta-tag ratio-tag">
+                                  <i class="el-icon-trend-chart"></i> {{ tag.likeRatio }}%
+                              </el-tag>
+                          </div>
+                      </div>
+                  </div>
+                  <span v-else class="no-tags">暂无标签</span>
+              </template>
+          </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="text"
+              @click="openCustomer(scope.row.userId,scope.row.idToString,scope.row.roboticId)"
+            >客户信息详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="callees.total>0"
+        :total="callees.total"
+        :page.sync="callees.queryParams.pageNum"
+        :limit.sync="callees.queryParams.pageSize"
+        @pagination="getCalleesList"
+      />
+    </el-drawer>
+    <el-drawer title="加个微详情" size="60%" :visible.sync="wx.show" append-to-body>
+      <el-table v-loading="wx.loading" :data="wx.list">
+        <el-table-column label="意向等级" align="center" prop="intention"/>
+        <el-table-column label="微信昵称" align="center" prop="wxNickName"/>
+        <el-table-column label="微信号" align="center" prop="wxNo"/>
+        <el-table-column label="手机号" align="center" prop="phone"/>
+        <el-table-column label="员工名称" align="center" prop="companyUserName"/>
+        <el-table-column label="话术" align="center" prop="dialogName"/>
+        <el-table-column label="分配数量" align="center" prop="num"/>
+        <el-table-column label="添加数量" align="center" prop="addNum"/>
+      </el-table>
+
+      <pagination
+        v-show="wx.total>0"
+        :total="wx.total"
+        :page.sync="wx.queryParams.pageNum"
+        :limit.sync="wx.queryParams.pageSize"
+        @pagination="getWxList"
+      />
+    </el-drawer>
+    <el-drawer title="加企微详情" size="60%" :visible.sync="qw.show" append-to-body>
+      <el-table v-loading="qw.loading" :data="qw.list">
+        <el-table-column label="意向等级" align="center" prop="intention"/>
+        <el-table-column label="企微昵称" align="center" prop="wxNickName"/>
+        <el-table-column label="企微id" align="center" prop="wxNo"/>
+        <el-table-column label="手机号" align="center" prop="phone"/>
+        <el-table-column label="话术" align="center" prop="dialogName"/>
+        <el-table-column label="分配数量" align="center" prop="num"/>
+        <el-table-column label="添加数量" align="center" prop="addNum"/>
+      </el-table>
+
+      <pagination
+        v-show="qw.total>0"
+        :total="qw.total"
+        :page.sync="qw.queryParams.pageNum"
+        :limit.sync="qw.queryParams.pageSize"
+        @pagination="getWxListQw"
+      />
+    </el-drawer>
+
+    <!-- 执行日志对话框 -->
+    <el-drawer
+      title="任务执行日志"
+      size="75%"
+      :visible.sync="execLogs.show"
+      append-to-body
+      class="exec-logs-drawer">
+      <div class="exec-logs-container" v-loading="execLogs.loading">
+        <el-card class="filter-card" shadow="never">
+          <el-form :inline="true" size="small" class="filter-form" style="margin-top: 20px;">
+            <el-form-item label="客户姓名">
+              <el-input
+                v-model="execLogs.customerName"
+                placeholder="请输入客户姓名"
+                clearable
+                @keyup.enter.native="handleSearch"
+                style="width: 180px">
+              </el-input>
+            </el-form-item>
+
+            <el-form-item label="手机号">
+              <el-input
+                v-model="execLogs.customerPhone"
+                placeholder="请输入手机号"
+                clearable
+                @keyup.enter.native="handleSearch"
+                style="width: 200px">
+              </el-input>
+            </el-form-item>
+
+            <el-form-item>
+              <span style="margin-right: 8px;">只看待执行外呼任务</span>
+              <el-checkbox v-model="execLogs.onlyCallNode"></el-checkbox>
+            </el-form-item>
+
+            <div style="margin-right:auto;">
+              <el-button type="primary" icon="el-icon-search" @click="handleSearch">
+                查询
+              </el-button>
+              <el-button icon="el-icon-refresh" @click="handleReset">
+                重置
+              </el-button>
+            </div>
+          </el-form>
+        </el-card>
+
+        <div v-if="execLogs.list.length === 0" class="empty-logs">
+          <i class="el-icon-document"></i>
+          <p>暂无执行记录</p>
+        </div>
+
+        <div v-else>
+          <!-- 统计信息卡片 -->
+          <el-card class="stats-card" shadow="never">
+            <div class="stats-grid">
+              <div class="stat-item">
+                <i class="el-icon-user" style="color: #1890ff;"></i>
+                <div class="stat-content">
+                  <div class="stat-label">客户总数</div>
+<!--                  <div class="stat-value">{{ execLogs.list.length }}</div>-->
+                  <div class="stat-value">{{ execLogs.total }}</div>
+                </div>
+              </div>
+              <div class="stat-item">
+                <i class="el-icon-phone" style="color: #52c41a;"></i>
+                <div class="stat-content">
+                  <div class="stat-label">已打电话</div>
+                  <div class="stat-value">{{ execLogs.stats.callDone }}</div>
+                </div>
+              </div>
+              <div class="stat-item">
+                <i class="el-icon-chat-line-square" style="color: #faad14;"></i>
+                <div class="stat-content">
+                  <div class="stat-label">已加微信</div>
+                  <div class="stat-value">{{ execLogs.stats.addWxDone }}</div>
+                </div>
+              </div>
+              <div class="stat-item">
+                <i class="el-icon-message" style="color: #722ed1;"></i>
+                <div class="stat-content">
+                  <div class="stat-label">已发短信</div>
+                  <div class="stat-value">{{ execLogs.stats.sendMsgDone }}</div>
+                </div>
+              </div>
+            </div>
+          </el-card>
+
+          <!-- 执行记录列表 -->
+          <div class="logs-list">
+            <el-collapse v-model="execLogs.activeNames" accordion>
+              <el-collapse-item
+                v-for="(record, index) in execLogs.list"
+                :key="index"
+                :name="index">
+                <template slot="title">
+                  <div class="record-header">
+                    <div class="record-left">
+                      <el-avatar :size="48" icon="el-icon-user-solid" :style="{background: getAvatarColor(index)}"></el-avatar>
+                      <div class="customer-detail">
+                        <div class="customer-name-row">
+                          <span class="customer-name">{{ record.customerName }}</span>
+
+                          <el-tag
+                            v-if="record.intention"
+                            size="small"
+                            type="info"
+                            class="status-tag">
+                            意向度:{{ getIntentionLabel(record.intention) }}
+                          </el-tag>
+
+                          <el-tag
+                            :type="getWorkflowStatusType(record.workflowStatus)"
+                            size="small"
+                            class="status-tag">
+                            {{ record.workflowStatusName }}
+                          </el-tag>
+                        </div>
+                        <div class="customer-meta">
+                          <span class="meta-item phone">
+                            <i class="el-icon-phone-outline"></i>
+                            {{ record.customerPhone }}
+                          </span>
+                          <span class="meta-divider">|</span>
+                          <span class="meta-item node">
+                            <i class="el-icon-s-operation"></i>
+                            当前节点:<strong>{{ record.currentNodeTypeName }}</strong>
+
+                            <!-- <el-button
+                              v-if="hasContent(record)"
+                              size="mini"
+                              type="primary"
+                              plain
+                              icon="el-icon-chat-dot-round"
+                              style="margin-left: 8px;"
+                              @click.stop="handleShowContent(record)">
+                                查看对话内容
+                            </el-button> -->
+                            <!-- 外呼未执行标识 -->
+                            <el-button
+                                v-if="record.waitCallNode"
+                                size="mini"
+                                type="primary"
+                                plain
+                                icon="el-icon-phone"
+                                style="margin-left: 8px;"
+                                @click.stop="handleManualCall(record)">
+                                开始人工外呼
+                            </el-button>
+                          </span>
+                        </div>
+                      </div>
+                    </div>
+                    <!-- <div class="record-right">
+                      <div class="progress-info">
+                        <span class="progress-label">执行进度</span>
+                        <span class="progress-value">{{ getProgress(record) }}%</span>
+                      </div>
+                      <el-progress
+                        :percentage="getProgress(record)"
+                        :color="getProgressColor(record)"
+                        :stroke-width="10"
+                        :show-text="false"></el-progress>
+                    </div> -->
+                  </div>
+                </template>
+
+                <!-- 节点执行日志 -->
+                <div class="node-logs">
+                  <el-timeline>
+                    <el-timeline-item
+                      v-for="(log, logIndex) in record.nodeLogs"
+                      :key="logIndex"
+                      :timestamp="log.startTime"
+                      :color="getNodeStatusColor(log.statusName)"
+                      placement="top">
+                      <el-card class="node-log-card">
+                        <div class="node-log-header">
+                          <div class="node-info">
+                            <i :class="getNodeIcon(log.nodeKey)" class="node-icon"></i>
+                            <span class="node-name">{{ log.nodeName }}</span>
+                            <el-button
+                              v-if="hasContent(log)"
+                              size="mini"
+                              type="primary"
+                              plain
+                              icon="el-icon-chat-dot-round"
+                              style="margin-left: 8px;"
+                              @click.stop="handleShowContent(record,log)">
+                                查看对话内容
+                            </el-button>
+                          </div>
+                          <el-tag
+                            :type="getStatusTagType(log.statusName)"
+                            size="mini">
+                            {{ log.statusName }}
+                          </el-tag>
+                        </div>
+                        <div class="node-log-body" v-if="log.errorMsg">
+                          <div class="error-msg">
+                            <i class="el-icon-warning"></i>
+                            {{ log.errorMsg }}
+                          </div>
+                        </div>
+                        <div class="node-log-footer">
+                          <span class="duration" v-if="log.duration">
+                            <i class="el-icon-time"></i>
+                            耗时: {{ formatDuration(log.duration) }}
+                          </span>
+                        </div>
+                      </el-card>
+                    </el-timeline-item>
+                  </el-timeline>
+                </div>
+              </el-collapse-item>
+            </el-collapse>
+          </div>
+          <!-- 分页 -->
+          <div class="pagination-wrapper" style="margin-top: 20px;">
+            <el-pagination
+              background
+              layout="total, sizes, prev, pager, next, jumper"
+              :total="execLogs.total"
+              :page-size="execLogs.pageSize"
+              :current-page.sync="execLogs.pageNum"
+              :page-sizes="[5, 10, 20, 30, 50]"
+              @current-change="handlePageChange"
+              @size-change="handleSizeChange">
+            </el-pagination>
+          </div>
+        </div>
+      </div>
+    </el-drawer>
+
+    <el-drawer size="75%" title="客户详情" :visible.sync="customerDetailShow" append-to-body>
+      <customer-details ref="customerDetails" />
+    </el-drawer>
+    <el-dialog
+      :title="manualCallDialog.title"
+      :visible.sync="manualCallDialog.visible"
+      width="1500px"
+      append-to-body
+      destroy-on-close
+      class="manual-call-dialog"
+      @close="handleManualCallDialogClose"
+    >
+      <call-center-phone-bar
+          :key="manualCallDialog.key"
+          ref="callCenterPhoneBar"
+          :init-phone-number="manualCallDialog.phone"
+          :robotic-id="manualCallDialog.roboticId"
+          :company-id="manualCallDialog.companyId"
+          :company-user-id="manualCallDialog.companyUserId"
+          :workflow-instance-id="manualCallDialog.workflowInstanceId"
+      />
+    </el-dialog>
+
+    <el-dialog
+      title="对话内容"
+      :visible.sync="contentDialog.visible"
+      width="900px"
+      append-to-body
+      class="content-dialog"
+    >
+      <div class="content-dialog-wrapper">
+          <div class="content-dialog-header" >
+              <span>客户姓名:{{ contentDialog.customerName || '-' }}</span>
+              <span>手机号:{{ contentDialog.customerPhone || '-' }}</span>
+          </div>
+
+          <div v-if="!contentDialog.content" class="content-empty">
+              暂无对话内容
+          </div>
+
+          <div class="chat-container">
+              <div
+                  v-for="(msg, index) in parseContentList(contentDialog.content)"
+                  :key="index"
+                  :class="['chat-item', msg.role === 'user' ? 'chat-right' : 'chat-left']"
+              >
+                  <div class="chat-bubble-wrapper">
+                      <div class="chat-role">
+                          {{ msg.role === 'user' ? '客户' : '客服' }}
+                      </div>
+                      <div class="chat-bubble">
+                          {{ msg.content }}
+                      </div>
+                  </div>
+              </div>
+          </div>
+
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import {
+    myListRobotic,
+  getRobotic,
+  delRobotic,
+  addRobotic,
+  updateRobotic,
+  exportRobotic,
+  calleesList,
+  statusList,
+  startRobotic,
+  stopRobotic,
+  companyUserList,
+  wxList,
+  taskRun,
+    wxListQw,
+  getSmsTempList,
+  // getCIDGroupList,
+  getExecRecords,
+  getCurrentCompanyId
+} from "@/api/company/companyVoiceRobotic";
+import draggable from 'vuedraggable'
+// import { listAll } from '@/api/company/wxDialog';
+import customerSelect from '@/views/crm/components/CustomerSelect.vue';
+import qwUserSelect from '@/views/components/QwUserSelect.vue';
+import qwUserSelectTwo from '@/views/components/QwUserSelectTwo.vue';
+import customerDetails from "@/views/crm/components/customerDetails.vue";
+import {getDicts} from "@/api/system/dict/data";
+import { optionList } from '@/api/company/companyWorkflow'
+import CallCenterPhoneBar from '../../aiSipCall/aiSipCallManualOutbound.vue'
+
+export default {
+  name: "myRobotic",
+  components: { draggable, customerDetails, customerSelect, qwUserSelect,qwUserSelectTwo,CallCenterPhoneBar},
+  data() {
+    return {
+      taskType:1,
+      taskTypeList:[{id:1,name:"普通任务"},{id:2,name:"场景任务"}],
+      currentCompanyId:null,
+      // 遮罩层
+      loading: true,
+      // CIDGroupList:[],
+      // 选中数组
+      ids: [],
+      weekList: [
+        {label: "星期一", value: 1},
+        {label: "星期二", value: 2},
+        {label: "星期三", value: 3},
+        {label: "星期四", value: 4},
+        {label: "星期五", value: 5},
+        {label: "星期六", value: 6},
+        {label: "星期日", value: 0},
+      ],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      loadingStatus: true,
+      // 总条数
+      total: 0,
+      // 机器人外呼任务表格数据
+      roboticList: [],
+      userTableList: [],
+      workflowList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        taskName: null,
+        taskId: null,
+        robot: null,
+        dialogId: null,
+        mode: null,
+        multiplier: null,
+        autoRecall: null,
+        recallTimes: null,
+        cidGroupId: null,
+        weekDay1: null,
+        startTime1: null,
+        endTime1: null,
+        weekDay2: null,
+        startTime2: null,
+        endTime2: null,
+        createUser: null,
+        taskType:null
+      },
+      // 表单参数
+      form: {
+        taskType:1
+      },
+      taskFlowList: [{key: "cellPhone", value: "外呼"}, {key: "qwAddWx", value: "企微加微"}, {key: "sendMsg", value: "发短信"}, {key: "addWx", value: "加微"}],
+      taskFlowMap: {cellPhone: "外呼", sendMsg: "发短信", addWx: "加微", qwAddWx: "企微加微"},
+      statusObj: {},
+      levelList: {},
+      robotList: [],
+      dialogList: [],
+      wxDialogList: [],
+      qwUserList: [],
+      selectQwUserList: [],
+      thisQwUserIndex: 0,
+      updateTime: null,
+      customer: {
+        show: false,
+      },
+      customerDetailShow: false,
+      callees: {
+        show: false,
+        list: [],
+        loading: false,
+        total: 0,
+        queryParams:{
+          id: null,
+          pageNum: 1,
+          pageSize: 10,
+        },
+      },
+      wx: {
+        show: false,
+        list: [],
+        loading: false,
+        total: 0,
+        queryParams:{
+          id: null,
+          pageNum: 1,
+          pageSize: 10,
+        },
+      },
+      qw: {
+        show: false,
+        list: [],
+        loading: false,
+        total: 0,
+        queryParams: {
+          id: null,
+          pageNum: 1,
+          pageSize: 10,
+        },
+      },
+      execLogs: {
+        show: false,
+        loading: false,
+        list: [],
+        activeNames: [],
+        stats: {
+          callDone: 0,
+          addWxDone: 0,
+          sendMsgDone: 0
+        },
+        pageNum: 1,
+        pageSize: 10,
+        total: 0,
+        currentTaskId: null,
+        customerName: '',
+        customerPhone: '',
+        onlyCallNode: false
+      },
+      manualCallDialog: {
+        visible: false,
+        phone: '',
+        title: "人工外呼",
+        record: null,
+        key: 0,
+        roboticId: null,
+        companyId: null,
+        companyUserId: null,
+        workflowInstanceId: null
+      },
+      contentDialog: {
+        visible: false,
+        content: '',
+        customerName: '',
+        customerPhone: ''
+      },
+      // 表单校验
+      rules: {
+        taskType:[
+           { required: true, message: '请选择任务类型', trigger: 'change' },
+        ],
+        name: [
+            { required: true, message: '请输入任务名称', trigger: 'blur' },
+          ],
+        companyAiWorkflowId: [
+            { required: true, message: '请选择流程', trigger: 'change' }
+          ],
+        runtimeRangeStart:[
+             { required: true, message: '请选择任务运行开始时间', trigger: 'change' }
+          ],
+        runtimeRangeEnd:[
+             { required: true, message: '请选择任务运行结束时间', trigger: 'change' }
+          ]
+      },
+      smsTempList:[],
+      sceneList:[]
+    };
+  },
+  created() {
+    getCurrentCompanyId().then(res=>{
+        this.currentCompanyId = res.companyId;
+    }).catch(res=>{
+      console.log(res);
+    })
+    //getTypes().then(e => {
+      //this.robotList = e.robot;
+      //this.dialogList = e.dialog;
+    //})
+    // listAll().then(e => {
+    //   this.wxDialogList = e.data;
+    // })
+    companyUserList().then(e => {
+      this.qwUserList = e.data;
+    })
+    optionList().then(e => {
+      this.workflowList = e.data;
+    })
+    getDicts("customer_intention_level").then(e => {
+      this.levelList = e.data;
+    })
+    getDicts("task_scene_type").then(e => {
+      console.log(e);
+      this.sceneList = e.data;
+    })
+    this.getList();
+    this.getSmsTempDropList();
+
+    // getCIDGroupList().then(res=>{
+    //   console.log("----------------------")
+    //   console.log(res);
+    //   this.CIDGroupList = res.data;
+    // }).catch(res=>{
+    //   console.log("catch_____+++++++")
+    //   console.log(res);
+    // });
+  },
+  watch: {
+    // 监听添加类型的切换,清空选择器数据
+    'form.isWeCom': {
+      handler(newVal, oldVal) {
+        // 只有当值真正发生变化时才执行清空操作
+        if (newVal !== oldVal && oldVal !== undefined) {
+          // 清空 qwUser 数组中的选择数据
+          if (this.form.qwUser && this.form.qwUser.length > 0) {
+            this.form.qwUser.forEach(item => {
+              item.companyUserId = []; // 清空已选择的账号ID
+            });
+          }
+
+          // 清空选择器列表数据
+          this.selectQwUserList = [];
+
+          // 重置当前选择索引
+          this.thisQwUserIndex = 0;
+
+          // 强制更新视图
+          this.$forceUpdate();
+        }
+      },
+      immediate: false
+    },
+    'execLogs.currentTaskId': {
+      handler(newVal, oldVal) {
+        // 只有当任务ID真的变化时才重置搜索条件
+        if (newVal !== oldVal && oldVal !== undefined) {
+          // 清空搜索条件
+          this.execLogs.customerName = ''
+          this.execLogs.customerPhone = ''
+          this.execLogs.pageNum = 1
+          this.execLogs.onlyCallNode = false
+
+          // 可选:重置分页
+          this.execLogs.pageSize = 10
+
+          // 重新获取执行记录
+          this.getExecLogs()
+        }
+      },
+      immediate: false
+    }
+  },
+  methods: {
+
+      getIntentionText(intention) {
+          const intentionMap = {
+              high: "高意向",
+              medium: "中意向",
+              low: "低意向",
+              none: "无意向"
+          };
+          return intentionMap[intention] || intention;
+      },
+    getSmsTempDropList(){
+      getSmsTempList().then(res=>{
+        this.smsTempList = res.data;
+        console.log(this.smsTempList);
+      }).catch(res=>{
+        console.log(res);
+      })
+    },
+    /** 查询机器人外呼任务列表 */
+    getList() {
+      this.loading = true;
+        myListRobotic(this.queryParams).then(response => {
+        this.roboticList = response.rows;
+        this.roboticList.forEach(e => {
+          if(e.weekDay1){
+            e.weekDay = e.weekDay1.split(",")
+          }
+        })
+        this.total = response.total;
+        this.loading = false;
+        this.updateStatusFun();
+      });
+    },
+    updateStatusFun(){
+      if(!this.roboticList){
+        return;
+      }
+      this.loadingStatus = true;
+      statusList(this.roboticList.map(e => e.taskId).join()).then(e => {
+        this.loadingStatus = false;
+        this.statusObj = e.data;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        taskName: null,
+        taskId: null,
+        robot: null,
+        dialogId: null,
+        mode: 7,
+        multiplier: 3,
+        autoRecall: 0,
+        recallTimes: null,
+        cidGroupId: null,
+        weekDay1: null,
+        startTime1: null,
+        addType: 0,
+        endTime1: null,
+        weekDay2: null,
+        startTime2: null,
+        endTime2: null,
+        createTime: null,
+        qwUser: [],
+        createUser: null,
+        userIds: [],
+        userNames: [],
+        userTableList: [],
+        companyAiWorkflowId: null,
+        runtimeRangeStart:null,
+        runtimeRangeEnd:null,
+        isWeCom: 1,
+        taskType:1,
+        availableStartTime :null,
+        availableEndTime: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      optionList().then(e => {
+      this.workflowList = e.data;
+      })
+      this.open = true;
+      this.title = "添加任务";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getRobotic(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改机器人外呼任务";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.weekDay && this.form.weekDay.length > 0) {
+            this.form.weekDay1 = this.form.weekDay.join(",")
+          }
+
+          // 验证加微方案
+          if(!this.form.qwUser || this.form.qwUser.length == 0){
+            this.msgError("请添加分配账号");
+            return;
+          }
+
+          // 验证每个账号是否选择了企微和话术
+          for(let i = 0; i < this.form.qwUser.length; i++) {
+            const account = this.form.qwUser[i];
+            if(!account.companyUserId || account.companyUserId.length == 0) {
+              this.msgError(`第 ${i + 1} 个账号请选择企微`);
+              return;
+            }
+            // if(!account.wxDialogId) {
+            //   this.msgError(`第 ${i + 1} 个账号请选择话术`);
+            //   return;
+            // }
+          }
+
+          let list = [];
+          this.form.qwUser.forEach(l => {
+            list = list.concat(l.companyUserId.map(e => {return {intention: l.intention, companyUserId: e,wxDialogId: l.wxDialogId,smsTempId:l.smsTempId}}))
+          })
+          this.form.qwUserList = list;
+          console.log(this.form);
+          // if(this.form.addType != 0 ){
+          //  let firstTask = this.taskFlowList[0];
+          //   if(firstTask.key != "cellPhone"){
+          //     this.msgError("【意向】加微方式下,任务流程第一步必须为外呼!");
+          //     return;
+          //   }
+          // }
+          if(!this.form.qwUserList || this.form.qwUserList.length == 0){
+            this.msgError("请选者加微方案");
+            return;
+          }
+          this.form.taskFlow = this.taskFlowList.map(e => e.key).join();
+          if (this.form.id != null) {
+            updateRobotic(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          } else {
+            addRobotic(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除机器人外呼任务编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delRobotic(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function () {
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有机器人外呼任务数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return exportRobotic(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+      }).catch(function () {
+      });
+    },
+    openSelect() {
+      if(!!this.currentCompanyId){
+      this.$refs.customer.setRowsDesignatedCompany(this.form.userTableList || [],this.currentCompanyId);
+      }else{
+      this.$refs.customer.setRows(this.form.userTableList || []);
+      }
+    },
+    // openQwUserSelect(index) {
+    //   this.thisQwUserIndex = index;
+    //   this.$nextTick(() => {
+    //     this.$refs.qwUserSelect.setRows(this.selectQwUserList[index]);
+    //   })
+    // },
+    selectFun(e) {
+      this.form.userIds = e.ids;
+      this.form.userNames = e.names;
+      this.form.userTableList = e.rows;
+      this.$forceUpdate()
+    },
+    selectQwUserFun(e) {
+      this.form.qwUser[this.thisQwUserIndex].companyUserId = e.ids;
+      this.selectQwUserList[this.thisQwUserIndex] = e.rows;
+      this.$forceUpdate()
+    },
+    calleesOpen(id){
+      this.callees.show = true;
+      this.callees.queryParams.id = id;
+      this.getCalleesList();
+    },
+    getCalleesList() {
+      this.callees.loading = true;
+      calleesList(this.callees.queryParams).then(response => {
+        this.callees.list = response.rows;
+        this.callees.total = response.total;
+        this.callees.loading = false;
+      });
+    },
+    wxOpen(id, isWeCom) {
+      console.log("isWeCom="+isWeCom)
+      if (isWeCom === 2) {
+        this.qw.show = true;
+        this.qw.queryParams.id = id;
+        this.getWxListQw();
+      } else {
+        this.wx.show = true;
+        this.wx.queryParams.id = id;
+        this.getWxList();
+      }
+    },
+    getWxListQw() {
+      this.qw.loading = true;
+      wxListQw(this.qw.queryParams).then(response => {
+        this.qw.list = response.rows;
+        this.qw.total = response.total;
+        this.qw.loading = false;
+      });
+    },
+    getWxList() {
+      this.wx.loading = true;
+      wxList(this.wx.queryParams).then(response => {
+        this.wx.list = response.rows;
+        this.wx.total = response.total;
+        this.wx.loading = false;
+      });
+    },
+    openCustomer(id,calleesId,roboticId) {
+      this.customerDetailShow=true;
+      this.$nextTick(() => {
+        this.$refs.customerDetails.getDetails(id,calleesId,roboticId);
+      })
+    },
+    startRoboticFun(id){
+      startRobotic(id).then(e => {
+        this.updateStatusFun();
+      })
+    },
+    taskRunFun(id){
+      taskRun({id}).then(e => {
+        this.getList();
+      })
+    },
+    stopRoboticFun(id){
+      stopRobotic(id).then(e => {
+        this.updateStatusFun();
+      })
+    },
+    addQwUser(){
+      this.form.qwUser.push({});
+    },
+    removeQwUser(index){
+      this.form.qwUser.splice(index, 1)
+    },
+    // 打开执行日志
+    async showExecLogs(row) {
+      this.execLogs.show = true;
+      // this.execLogs.loading = true;
+      // this.execLogs.list = [];
+      this.execLogs.pageNum = 1;
+      this.execLogs.pageSize = 10;
+      this.execLogs.currentTaskId = row.id;
+
+      try {
+        // const res = await getExecRecords(row.id);
+        // this.execLogs.list = res.data || [];
+        this.getExecLogs();
+
+        // 计算统计数据
+        // this.execLogs.stats = {
+        //   callDone: this.execLogs.list.reduce((sum, r) => sum + (r.callPhoneDone || 0), 0),
+        //   addWxDone: this.execLogs.list.reduce((sum, r) => sum + (r.addWxDone || 0), 0),
+        //   sendMsgDone: this.execLogs.list.reduce((sum, r) => sum + (r.sendMsgDone || 0), 0)
+        // };
+      } catch (error) {
+        console.error('获取执行日志失败:', error);
+        this.$message.error('获取执行日志失败');
+      } finally {
+        this.execLogs.loading = false;
+      }
+    },
+    // 获取工作流状态类型
+    getWorkflowStatusType(status) {
+      const typeMap = {
+        1: '',
+        2: 'success',
+        3: 'warning',
+        4: 'danger'
+      };
+      return typeMap[status] || 'info';
+    },
+    // 获取进度
+    getProgress(record) {
+      if (!record.nodeLogs || record.nodeLogs.length === 0) return 0;
+      const total = record.nodeLogs.length;
+      const completed = record.nodeLogs.filter(log => log.statusName === '执行成功').length;
+      return Math.round((completed / total) * 100);
+    },
+    // 获取进度条颜色
+    getProgressColor(record) {
+      const progress = this.getProgress(record);
+      if (progress === 100) return '#52c41a';
+      if (progress >= 50) return '#1890ff';
+      return '#faad14';
+    },
+    // 获取节点状态颜色
+    getNodeStatusColor(statusName) {
+      const colorMap = {
+        '执行成功': '#52c41a',
+        '执行中': '#1890ff',
+        '执行失败': '#f5222d',
+        '等待执行': '#d9d9d9',
+        '执行超时':'#f5222d'
+      };
+      return colorMap[statusName] || '#d9d9d9';
+    },
+    // 获取状态标签类型
+    getStatusTagType(statusName) {
+      const typeMap = {
+        '执行成功': 'success',
+        '执行中': 'warning',
+        '执行失败': 'danger',
+        '等待执行': 'info',
+        '执行超时':'danger'
+      };
+      return typeMap[statusName] || 'info';
+    },
+    // 获取节点图标
+    getNodeIcon(nodeKey) {
+      const iconMap = {
+        'node_start': 'el-icon-video-play',
+        'node_addwx': 'el-icon-chat-line-square',
+        'node_call': 'el-icon-phone',
+        'node_sms': 'el-icon-message',
+        'node_end': 'el-icon-circle-check'
+      };
+      return iconMap[nodeKey] || 'el-icon-document';
+    },
+    // 格式化时长
+    formatDuration(ms) {
+      if (!ms) return '-';
+      if (ms < 1000) return ms + 'ms';
+      const seconds = Math.floor(ms / 1000);
+      if (seconds < 60) return seconds + 's';
+      const minutes = Math.floor(seconds / 60);
+      const remainSeconds = seconds % 60;
+      return minutes + 'm' + remainSeconds + 's';
+    },
+    // 获取当前应该使用的组件
+    getCurrentComponent() {
+      return this.form.isWeCom === 2 ? 'qwUserSelectTwo' : 'qwUserSelect';
+    },
+
+    openQwUserSelect(index) {
+      this.thisQwUserIndex = index;
+      this.$nextTick(() => {
+        // 根据 isWeCom 的值选择对应的组件引用
+        const componentRef = this.form.isWeCom === 2 ? this.$refs.dynamicQwUserSelect : this.$refs.dynamicQwUserSelect;
+        if (componentRef && typeof componentRef.setRows === 'function') {
+          componentRef.setRows(this.selectQwUserList[index]);
+        }
+      })
+    },
+    // 获取头像颜色
+    getAvatarColor(index) {
+      const colors = ['#1890ff', '#52c41a', '#faad14', '#722ed1', '#eb2f96'];
+      return colors[index % colors.length];
+    },
+    /**
+     * 选择任务类型
+     */
+    taskTypeChange(){
+      console.log(this.form.taskType);
+      this.form.runtimeRangeStart=null;
+      this.form.runtimeRangeEnd= null;
+      this.form.userIds= [];
+      this.form.userNames= [];
+      this.form.userTableList= [];
+      this.form.companyAiWorkflowId = null;
+      this.form.name = null;
+      this.form.availableStartTime = null;
+      this.form.availableEndTime = null;
+      this.$nextTick(() => {
+      this.$refs.form.clearValidate();
+      })
+    },
+    async getExecLogs() {
+      this.execLogs.loading = true
+      try {
+        const res = await getExecRecords({
+          roboticId: this.execLogs.currentTaskId,
+          pageNum: this.execLogs.pageNum,
+          pageSize: this.execLogs.pageSize,
+          customerName: this.execLogs.customerName,
+          customerPhone: this.execLogs.customerPhone,
+          onlyCallNode: this.execLogs.onlyCallNode
+        })
+        this.execLogs.list = (res.rows || []).map(item => ({
+          ...item,
+          contentList: item.contentList || '',
+          intention: item.intention || ''
+        }))
+        this.execLogs.total = res.total || 0
+        // this.execLogs.stats = res.stats || {
+        //   callDone: 0,
+        //   addWxDone: 0,
+        //   sendMsgDone: 0
+        // }
+        // 前端计算统计数据
+        this.execLogs.stats = {
+          callDone: this.execLogs.list.reduce((sum, r) => sum + (Number(r.callPhoneDone) || 0), 0),
+          addWxDone: this.execLogs.list.reduce((sum, r) => sum + (Number(r.addWxDone) || 0), 0),
+          sendMsgDone: this.execLogs.list.reduce((sum, r) => sum + (Number(r.sendMsgDone) || 0), 0)
+        }
+
+      } catch (e) {
+        this.$message.error("获取执行日志失败")
+      } finally {
+        this.execLogs.loading = false
+      }
+    },
+
+    handlePageChange(page) {
+      this.execLogs.pageNum = page
+      this.getExecLogs()
+    },
+
+    handleSizeChange(size) {
+      this.execLogs.pageSize = size
+      this.execLogs.pageNum = 1
+      this.getExecLogs()
+    },
+    handleSearch() {
+      // 查询时回到第一页
+      this.execLogs.pageNum = 1
+      this.getExecLogs()
+    },
+
+    handleReset() {
+      this.execLogs.customerName = ''
+      this.execLogs.customerPhone = ''
+      this.execLogs.pageNum = 1
+      this.execLogs.onlyCallNode = false
+      this.getExecLogs()
+    },
+    handleManualCall(record) {
+      const phone = record.customerPhone || '';
+
+      if (!phone) {
+          this.$message.warning('当前客户没有可外呼的手机号');
+          return;
+      }
+
+      this.manualCallDialog.record = record;
+      this.manualCallDialog.phone = phone;
+      this.manualCallDialog.title = `人工外呼 - ${record.customerName || '未知客户'}`;
+
+      const currentTask = this.roboticList.find(
+        item => item.id === this.execLogs.currentTaskId
+      );
+      // 带过去的额外参数
+      this.manualCallDialog.roboticId = record.roboticId || null;
+      this.manualCallDialog.companyId = (currentTask && currentTask.companyId) || null;
+      this.manualCallDialog.companyUserId = (currentTask && currentTask.companyUserId) || null;
+      this.manualCallDialog.workflowInstanceId = record.workflowInstanceId || null;
+      this.manualCallDialog.key += 1;
+      this.manualCallDialog.visible = true;
+    },
+    handleManualCallDialogClose() {
+      this.manualCallDialog.phone = '';
+      this.manualCallDialog.record = null;
+      this.manualCallDialog.roboticId = null;
+      this.manualCallDialog.companyId = null;
+      this.manualCallDialog.companyUserId = null;
+      this.manualCallDialog.workflowInstanceId = null;
+    },
+     handleShowContent(record,log) {
+      this.contentDialog.customerName = record.customerName || '';
+      this.contentDialog.customerPhone = record.customerPhone || '';
+      this.contentDialog.content = log.nodeContentList || '';
+      this.contentDialog.visible = true;
+    },
+
+    parseContentList(content) {
+      if (!content) return []
+
+      try {
+          const parsed = typeof content === 'string' ? JSON.parse(content) : content
+          if (!Array.isArray(parsed)) return []
+
+          return parsed.filter(item => {
+              // 过滤 system 提示词
+              if (!item) return false
+              if (item.role === 'system') return false
+
+              // 过滤空内容
+              const text = item.content || ''
+              if (!String(text).trim()) return false
+
+              return true
+          })
+      } catch (e) {
+          console.error("解析 contentList 失败", e)
+          return []
+      }
+    },
+    hasContent(record) {
+      if (!record || !record.nodeContentList) return false
+
+      try {
+          const parsed = typeof record.nodeContentList === 'string'
+              ? JSON.parse(record.nodeContentList)
+              : record.nodeContentList
+
+          if (!Array.isArray(parsed)) return false
+
+          // 过滤 system 和空内容
+          const validList = parsed.filter(item => {
+              if (!item) return false
+              if (item.role === 'system') return false
+
+              const text = String(item.content || '').trim()
+              if (!text) return false
+
+              return true
+          })
+
+          return validList.length > 0
+      } catch (e) {
+          return false
+      }
+    },
+    getIntentionLabel(val) {
+      if (val === null || val === undefined || val === '') {
+          return '-'
+      }
+
+      const item = (this.levelList || []).find(e => String(e.dictValue) === String(val))
+      return item ? item.dictLabel : val
+    },
+  }
+};
+</script>
+<style>
+.flow-parent{
+  display: flex;
+  flex-wrap: nowrap;
+}
+.flow-child{
+  position: relative;
+  width: 150px;
+  cursor: move;
+}
+.flow-child:last-child i{
+  display: none;
+}
+.flow-child:last-child::after{
+  display: none;
+}
+.flow-child i{
+  right: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+  position: absolute;
+  font-weight: bold;
+}
+.flow-child::after{
+  content: "";
+  border-top: 1px solid #000;
+  height: 1px;
+  width: 50px;
+  right: 26px;
+  top: 50%;
+  transform: translateY(-50%);
+  position: absolute;
+}
+.sortable-ghost{
+  /* background: #FFF !important; */
+  background: rgb(217, 236, 255) !important;
+}
+
+/* 执行日志样式 */
+.exec-logs-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 60px);
+}
+
+.exec-logs-container ::v-deep .el-card {
+  border: none;
+  box-shadow: none;
+}
+
+.empty-logs {
+  text-align: center;
+  padding: 80px 20px;
+  color: #909399;
+}
+
+.empty-logs i {
+  font-size: 64px;
+  margin-bottom: 16px;
+  color: #dcdfe6;
+}
+
+.empty-logs p {
+  font-size: 16px;
+}
+
+/* 统计卡片 */
+.stats-card {
+  margin-bottom: 20px;
+  border-radius: 8px;
+  border: none;
+}
+
+.stats-card ::v-deep .el-card__body {
+  padding: 20px;
+}
+
+.stats-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 20px;
+}
+
+.stat-item {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 12px;
+  background: #f9fafb;
+  border-radius: 6px;
+}
+
+.stat-item i {
+  font-size: 32px;
+}
+
+.stat-content {
+  flex: 1;
+}
+
+.stat-label {
+  font-size: 13px;
+  color: #909399;
+  margin-bottom: 4px;
+}
+
+.stat-value {
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+/* 日志列表 */
+.logs-list {
+  background: #fff;
+  border-radius: 8px;
+  padding: 16px;
+  border: none;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+.el-collapse-item__header{
+  height: auto !important;
+}
+/* 记录头部 */
+.record-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  padding: 20px 16px;
+  gap: 32px;
+}
+
+.record-left {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  flex: 1;
+  min-width: 0;
+}
+
+.customer-detail {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.customer-name-row {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.customer-name {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+  line-height: 1.4;
+}
+
+.status-tag {
+  flex-shrink: 0;
+}
+
+.customer-meta {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  font-size: 13px;
+  color: #606266;
+  line-height: 1.4;
+}
+
+.meta-item {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.meta-item i {
+  font-size: 14px;
+  color: #909399;
+}
+
+.meta-item.node strong {
+  color: #1890ff;
+  font-weight: 600;
+}
+
+.meta-divider {
+  color: #dcdfe6;
+}
+
+.record-right {
+  width: 200px;
+  flex-shrink: 0;
+}
+
+.progress-info {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.progress-label {
+  font-size: 13px;
+  color: #909399;
+}
+
+.progress-value {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+/* 节点日志 */
+.node-logs {
+  padding: 20px;
+  background: #fafafa;
+  border-radius: 6px;
+}
+
+.node-log-card {
+  margin-bottom: 0;
+}
+
+.node-log-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.node-info {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.node-icon {
+  font-size: 18px;
+  color: #1890ff;
+}
+
+.node-name {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.node-log-body {
+  margin: 12px 0;
+}
+
+.error-msg {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 12px;
+  background: #fff2e8;
+  border-left: 3px solid #faad14;
+  border-radius: 4px;
+  font-size: 13px;
+  color: #d46b08;
+}
+
+.error-msg i {
+  font-size: 16px;
+}
+
+.node-log-footer {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  font-size: 13px;
+  color: #909399;
+}
+
+.duration {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.duration i {
+  font-size: 14px;
+}
+
+/* 任务表单样式 */
+.task-form-drawer ::v-deep .el-drawer__body {
+  padding: 0;
+}
+
+.drawer-content {
+  padding: 16px;
+  background: #f5f7fa;
+  min-height: 100%;
+}
+
+.task-form {
+  max-width: 750px;
+  margin: 0 auto;
+}
+
+.form-section {
+  background: #fff;
+  border-radius: 8px;
+  padding: 16px 20px;
+  margin-bottom: 16px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+.section-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 15px;
+  font-weight: 600;
+  color: #303133;
+  margin-bottom: 16px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #f0f2f5;
+}
+
+.section-title i {
+  font-size: 16px;
+  color: #1890ff;
+}
+
+.task-form ::v-deep .el-form-item {
+  margin-bottom: 16px;
+}
+
+.task-form ::v-deep .el-form-item:last-child {
+  margin-bottom: 0;
+}
+
+.task-form ::v-deep .el-input,
+.task-form ::v-deep .el-select {
+  width: 100%;
+}
+
+.task-form ::v-deep .el-radio-group {
+  width: 100%;
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 12px;
+}
+
+.task-form ::v-deep .el-radio.is-bordered {
+  margin: 0;
+  padding: 10px 16px;
+  height: auto;
+}
+
+.task-form ::v-deep .el-radio.is-bordered i {
+  margin-right: 6px;
+  font-size: 16px;
+}
+
+.account-list {
+  margin-top: 12px;
+}
+
+.account-item {
+  margin-bottom: 10px;
+  padding: 12px;
+  background: #f9fafb;
+  border-radius: 6px;
+  border: 1px solid #e8e8e8;
+}
+
+.account-item:hover {
+  border-color: #1890ff;
+  background: #f0f9ff;
+}
+
+.account-item ::v-deep .el-select {
+  width: 100%;
+}
+.chat-container {
+    max-height: 600px;
+    overflow-y: auto;
+    padding: 16px;
+    background: #f5f7fa;
+    border-radius: 8px;
+}
+
+.chat-item {
+    display: flex;
+    margin-bottom: 14px;
+}
+
+.chat-left {
+    justify-content: flex-start;
+}
+
+.chat-right {
+    justify-content: flex-end;
+}
+
+.chat-bubble-wrapper {
+    max-width: 75%;
+}
+
+.chat-role {
+    font-size: 12px;
+    color: #909399;
+    margin-bottom: 4px;
+}
+
+.chat-left .chat-role {
+    text-align: left;
+}
+
+.chat-right .chat-role {
+    text-align: right;
+}
+
+.chat-bubble {
+    padding: 10px 14px;
+    border-radius: 12px;
+    font-size: 14px;
+    line-height: 1.6;
+    word-break: break-word;
+    white-space: pre-wrap;
+}
+
+.chat-left .chat-bubble {
+    background: #fff;
+    border: 1px solid #ebeef5;
+    color: #303133;
+}
+
+.chat-right .chat-bubble {
+    background: #409eff;
+    color: #fff;
+}
+
+.content-dialog-header {
+    margin-top: -12px;
+    margin-bottom: 24px;
+    padding-bottom: 8px;
+    border-bottom: 1px solid #ebeef5;
+    display: flex;
+    align-items: center;
+    gap: 20px;
+    font-size: 14px;
+    color: #606266;
+}
+
+.ai-tags-container {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+    padding: 4px 0;
+    max-height: 270px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    scroll-behavior: smooth;
+}
+
+.ai-tags-container::-webkit-scrollbar {
+    width: 6px;
+}
+
+.ai-tags-container::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 3px;
+}
+
+.ai-tags-container::-webkit-scrollbar-thumb {
+    background: #c0c4cc;
+    border-radius: 3px;
+    transition: background 0.3s ease;
+}
+
+.ai-tags-container::-webkit-scrollbar-thumb:hover {
+    background: #909399;
+}
+
+.ai-tag-item {
+    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    padding: 10px 12px;
+    transition: all 0.3s ease;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
+    flex-shrink: 0;
+}
+
+.ai-tag-item:hover {
+    border-color: #c0c4cc;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+    transform: translateY(-1px);
+}
+
+.tag-main-content {
+    display: flex;
+    align-items: baseline;
+    gap: 8px;
+    margin-bottom: 6px;
+}
+
+.tag-property-name {
+    font-size: 13px;
+    color: #909399;
+    font-weight: 500;
+    white-space: nowrap;
+    flex-shrink: 0;
+}
+
+.tag-property-value {
+    font-size: 13px;
+    color: #303133;
+    font-weight: 600;
+    flex: 1;
+    word-break: break-all;
+}
+
+.tag-meta-info {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding-top: 6px;
+    border-top: 1px dashed #e4e7ed;
+}
+
+.meta-tag {
+    font-size: 12px;
+    padding: 0 8px;
+    height: 22px;
+    line-height: 20px;
+    border-radius: 4px;
+    font-weight: 500;
+}
+
+.meta-tag i {
+    margin-right: 3px;
+    font-size: 12px;
+}
+
+.intention-tag {
+    background: linear-gradient(135deg, #fff1f0 0%, #ffffff 100%);
+    border-color: #ffa39e;
+    color: #cf1322;
+}
+
+.ratio-tag {
+    background: linear-gradient(135deg, #f6ffed 0%, #ffffff 100%);
+    border-color: #b7eb8f;
+    color: #389e0d;
+}
+
+.no-tags {
+    color: #c0c4cc;
+    font-size: 13px;
+    font-style: italic;
+}
+
+</style>

+ 32 - 0
src/views/company/companyWorkflow/design.vue

@@ -448,6 +448,23 @@
                   />
                 </el-select>
               </el-form-item>
+
+              <el-form-item label="音色来源">
+                <el-select
+                    :disabled="editAiDisable"
+                    v-model="selectedNode.nodeConfig.voiceSource"
+                    placeholder="请选择音色来源"
+                    @change="handleConfigChange('handleVoiceSource')"
+                >
+                    <el-option
+                        v-for="item in voiceSourceOptions"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value"
+                    />
+                </el-select>
+              </el-form-item>
+
               <el-form-item label="音色">
                 <el-select
                   :disabled="editAiDisable"
@@ -743,7 +760,13 @@ export default {
       easyCallGatewayList: [],     // 外呼线路(网关)列表
       easyCallLlmAccountList: [],  // 大模型底座列表
       easyCallVoiceCodeList: [],   // 音色列表
+      allEasyCallVoiceCodeList: [],   // 接口原始数据
       easyCallBusiGroupList: [],   // tts厂商(技能组)列表
+      voiceSourceOptions: [
+        { label: '阿里云TTS', value: 'aliyun_tts' },
+        { label: '豆包语音', value: 'doubao_vcl_tts' },
+        { label: '电信语音', value: 'chinatelecom_tts' }
+      ],
     }
   },
   created() {
@@ -802,6 +825,14 @@ export default {
 
     // 处理配置变化,强制更新视图
     handleConfigChange(v) {
+      if( !!v && v === "handleVoiceSource" ){
+        let voiceSource = this.selectedNode.nodeConfig.voiceSource;
+
+        this.easyCallVoiceCodeList = this.allEasyCallVoiceCodeList.filter(e => e.voiceSource == voiceSource);
+
+        this.selectedNode.nodeConfig.voiceCode = null;
+        this.selectedNode.nodeConfig.ttsModels = null;
+      }
       if( !!v && v === "handleVoice" ){
        let voice =  this.easyCallVoiceCodeList.filter(e=>e.voiceCode == this.selectedNode.nodeConfig.voiceCode);
        this.selectedNode.nodeConfig.voiceSource = voice[0].voiceSource;
@@ -886,6 +917,7 @@ export default {
           this.easyCallLlmAccountList = res.data || []
         })
         getVoiceCodeList().then(res => {
+          this.allEasyCallVoiceCodeList = res.data || []
           this.easyCallVoiceCodeList = res.data || []
         })
         getBusiGroupList().then(res => {

+ 2 - 0
src/views/company/companyWorkflow/index.vue

@@ -89,6 +89,8 @@
           />
         </template>
       </el-table-column>
+      <el-table-column label="创建人" align="center" prop="createByName"/>
+      <el-table-column label="创建部门" align="center" prop="createByDeptName"/>
       <el-table-column label="版本" align="center" prop="version" width="60" />
       <el-table-column label="创建时间" align="center" prop="createTime" width="160">
         <template slot-scope="scope">

+ 657 - 0
src/views/company/companyWorkflow/myIndex.vue

@@ -0,0 +1,657 @@
+<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"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+        >删除</el-button>
+      </el-col>
+      <el-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 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="createByName"/>
+      <el-table-column label="创建部门" align="center" prop="createByDeptName"/>
+      <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)"
+          >设计</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)"
+          >复制</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleVersion(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"
+    />
+
+    <!-- 绑定销售弹窗 -->
+    <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>
+
+
+    <!-- 历史版本弹窗 -->
+    <el-dialog
+      title="历史版本"
+      :visible.sync="versionOpen"
+      width="60%"
+      custom-class="version-dialog"
+      append-to-body
+    >
+      <el-table
+          v-loading="versionLoading"
+          :data="versionList"
+          border
+          size="small"
+      >
+          <el-table-column label="版本号" align="center" width="120">
+              <template slot-scope="scope">
+                  <el-tag v-if="scope.row.versionNo === currentVersionNo" type="warning">
+                      v{{ scope.row.versionNo }}(当前)
+                  </el-tag>
+                  <el-tag v-else type="success">
+                      v{{ scope.row.versionNo }}
+                  </el-tag>
+              </template>
+          </el-table-column>
+
+          <el-table-column label="工作流名称" align="center" prop="workflowName" min-width="60" />
+
+          <el-table-column label="快照时间" align="center" width="170">
+              <template slot-scope="scope">
+                  <span>{{scope.row.snapshotTime }}</span>
+              </template>
+          </el-table-column>
+
+          <el-table-column label="操作人" align="center" prop="snapshotBy" width="120" />
+
+          <el-table-column label="操作" align="center" width="180">
+              <template slot-scope="scope">
+                  <el-button
+                      type="text"
+                      size="mini"
+                      @click="handleViewVersion(scope.row)"
+                  >查看</el-button>
+                  <el-button
+                      v-if="versionList.length > 1 && scope.row.versionNo !== currentVersionNo"
+                      type="text"
+                      size="mini"
+                      style="color: #f56c6c"
+                      @click="handleRollbackVersion(scope.row)"
+                  >回退</el-button>
+              </template>
+          </el-table-column>
+      </el-table>
+
+      <div slot="footer" class="dialog-footer">
+          <el-button @click="versionOpen = false">关闭</el-button>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import { myListWorkflow, delWorkflow, updateWorkflowStatus, copyWorkflow, exportWorkflow, getBindCompanyUserByWorkflowId,
+         listCompanyUser, updateWorkflowBindCompanyUser, getWorkflowVersionList, getWorkflowVersionDetail, rollbackWorkflowVersion} from '@/api/company/companyWorkflow'
+
+export default {
+  name: 'myCompanyWorkflow',
+  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: '',
+      // 历史版本弹窗
+      versionOpen: false,
+      // 版本列表
+      versionList: [],
+      // 加载状态
+      versionLoading: false,
+      // 当前版本详情弹窗
+      versionDetailOpen: false,
+      // 当前版本详情加载
+      versionDetailLoading: false,
+      // 当前版本详情
+      versionDetail: {
+        workflowVersion: {},
+        nodes: [],
+        edges: []
+      },
+      currentVersionNo: null,
+    }
+  },
+  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
+    // })
+  },
+  watch: {
+    '$route.query.openVersionWorkflowId': {
+        handler(val) {
+            if (val) {
+                this.$nextTick(() => {
+                    this.autoOpenVersionDialog(val)
+                })
+            }
+        },
+        immediate: true
+    }
+  },
+  methods: {
+    /** 查询工作流列表 */
+    getList() {
+        this.loading = true
+        myListWorkflow(this.queryParams).then(response => {
+            this.workflowList = response.rows
+            this.total = response.total
+            this.loading = false
+
+            const workflowId = this.$route.query.openVersionWorkflowId
+            if (workflowId) {
+                this.$nextTick(() => {
+                    this.autoOpenVersionDialog(workflowId)
+                })
+            }
+        }).catch(() => {
+            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('/companyWx/companyWorkflow/design')
+    },
+    /** 设计按钮操作 */
+    handleEdit(row) {
+      this.$router.push('/companyWx/companyWorkflow/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()
+      })
+    },
+    /** 打开历史版本弹窗 */
+    handleVersion(row) {
+      this.currentWorkflow = row
+      this.currentVersionNo = row.version
+      this.versionOpen = true
+      this.versionLoading = true
+      this.versionList = []
+
+      getWorkflowVersionList(row.workflowId).then(res => {
+          this.versionList = res.data || []
+          this.versionLoading = false
+      }).catch(() => {
+          this.versionLoading = false
+      })
+    },
+
+    /** 查看版本详情 */
+    handleViewVersion(row) {
+        this.$router.push({
+            path: '/companyWorkflow/version-preview/' + row.versionId,
+            query: {
+                workflowId: this.currentWorkflow.workflowId
+            }
+        })
+    },
+
+    /** 一键回退 */
+    handleRollbackVersion(row) {
+      this.$confirm('是否确认回退到版本 v' + row.versionNo + ' ?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+      }).then(() => {
+          return rollbackWorkflowVersion(row.versionId)
+      }).then(() => {
+          this.msgSuccess('回退成功')
+          this.versionOpen = false
+          this.versionDetailOpen = false
+          this.getList()
+      }).catch(() => {})
+    },
+    /** 自动打开历史版本弹窗 */
+    autoOpenVersionDialog(workflowId) {
+      const row = this.workflowList.find(item => String(item.workflowId) === String(workflowId))
+      if (!row) {
+          return
+      }
+      this.handleVersion(row)
+
+      // 清掉 query,避免重复触发
+      const query = { ...this.$route.query }
+      delete query.openVersionWorkflowId
+      this.$router.replace({
+          path: this.$route.path,
+          query
+      })
+    }
+  }
+}
+</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>

+ 0 - 8
src/views/company/wxAccount/index.vue

@@ -149,9 +149,6 @@
             <el-option v-for="item in qwUserList" :label="item.nickName" :value="item.userId" />
           </el-select>
         </el-form-item>
-          <el-form-item label="微信备注前缀" prop="wxRemark">
-              <el-input :disabled="title=='修改个微账号'" v-model="form.wxRemark"  :maxlength="6" placeholder="请输入六位微信备注前缀" />
-          </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -299,11 +296,6 @@ export default {
           ],
           companyUserId: [
               { required: true, message: '请选择员工', trigger: 'change' }
-          ],
-          wxRemark: [
-              { required: true, message: '请输入微信备注前缀', trigger: 'blur' },
-              { len: 6, message: '微信备注前缀必须为6位', trigger: 'blur' },
-              { pattern: /^\d{6}$/, message: '微信备注前缀只能输入6位数字', trigger: 'blur' }
           ]
       },
         // 联系人弹窗

+ 54 - 3
src/views/components/QwUserSelect.vue

@@ -328,14 +328,65 @@ export default {
     margin-left: 10px;
     vertical-align: bottom;
   }
-  .el-dialog__wrapper{
+  .el-dialog__wrapper {
     z-index: 100000;
   }
-  .app-container{padding: 0}
-  .dialog-footer{
+  .app-container {
+    padding: 0;
+  }
+  .dialog-footer {
     position: absolute;
     bottom: 0;
     right: 20px;
     background: #FFF;
   }
+
+  .drawer-container {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    padding: 0 16px;
+  }
+
+  .search-card {
+    margin-bottom: 12px;
+  }
+  .search-card >>> .el-card__body {
+    padding: 16px 16px 0;
+  }
+  .search-form >>> .el-form-item {
+    margin-bottom: 16px;
+  }
+  .search-form >>> .el-form-item__content {
+    width: calc(100% - 80px);
+  }
+  .search-form >>> .el-select,
+  .search-form >>> .el-date-editor {
+    width: 100%;
+  }
+
+  .table-card {
+    flex: 1;
+    overflow: hidden;
+    margin-bottom: 12px;
+  }
+  .table-card >>> .el-card__body {
+    padding: 12px 16px;
+  }
+  .card-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+
+  .footer-actions {
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+    padding: 12px 0;
+    border-top: 1px solid #EBEEF5;
+  }
+  .footer-actions .el-button + .el-button {
+    margin-left: 10px;
+  }
 </style>

+ 24 - 5
src/views/course/courseWatchLog/deptWatchLog.vue

@@ -92,7 +92,11 @@
             :key="dict.dictValue"
             :label="dict.dictLabel"
             :value="dict.dictValue"
-          />
+          >
+            <span style="float: left">{{ dict.dictLabel }}</span>
+            <span style="float: right; color: #8492a6; font-size: 13px">ID: {{ dict.dictValue }}</span>
+            <span v-if="dict.isDel === 1" style="float: right; color: #f56c6c; font-size: 13px; margin-right: 10px">已删除</span>
+          </el-option>
         </el-select>
       </el-form-item>
       <!-- sop名称 -->
@@ -463,7 +467,12 @@
 <!--        <el-table-column label="会员电话" align="center" prop="phone" />-->
 <!--        <el-table-column label="所属销售" align="center" prop="companyUserName" />-->
 <!--        <el-table-column label="所属公司" align="center" prop="companyName" />-->
-        <el-table-column label="转账金额" align="center" prop="amount" />
+<!--        <el-table-column label="转账金额" align="center" prop="amount" />-->
+          <el-table-column :label="RewardType === 1 ? '转账金额' : '积分发放(红包转)'" align="center">
+              <template slot-scope="scope">
+                  <span>{{ RewardType === 1 ? scope.row.amount : scope.row.amount * 1000 }}</span>
+              </template>
+          </el-table-column>
         <el-table-column label="状态" align="center" prop="status" >
           <template slot-scope="scope">
             <el-tag>
@@ -477,6 +486,7 @@
         </el-table-column>
         <el-table-column label="所属企微" align="center" prop="qwUserName" />
         <el-table-column label="创建时间" align="center" prop="createTime" />
+          <el-table-column label="备注" align="center" prop="remark" />
       </el-table>
 
       <pagination
@@ -521,7 +531,7 @@ import {
   getCourseWatchLog,
   updateCourseWatchLog
 } from "@/api/course/courseWatchLog";
-import {courseList, myListCourseRedPacketLog, videoList} from '@/api/course/courseRedPacketLog'
+import {courseList, myListCourseRedPacketLog, videoListByWatch} from '@/api/course/courseRedPacketLog'
 import {myListLogs} from "@/api/course/courseAnswerlogs";
 import {getCompanyUserListLikeName} from "@/api/company/companyUser";
 import {getTask} from "@/api/common";
@@ -531,6 +541,7 @@ import {infoSop} from "@/api/qw/sop";
 import {myDeptTreeselect} from "../../../api/company/companyDept";
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import {getCourseConfigByRewardType} from "../../../api/system/config";
 
 Vue.use(Calendar)
 
@@ -540,6 +551,7 @@ export default {
   components: {Treeselect},
   data() {
     return {
+      RewardType: 1,
       companyName:process.env.VUE_APP_COURSE_COMPANY_NAME,
       sopSearchText: '', // SOP搜索框显示的文本
       selectedSopId: null, // 选中的SOP ID
@@ -693,6 +705,9 @@ export default {
     };
   },
   created() {
+
+    this.getCourseByRewardType();
+
     courseList().then(response => {
       this.courseLists = response.list;
     });
@@ -711,7 +726,11 @@ export default {
     this.loading=false;
   },
   methods: {
-
+      getCourseByRewardType(){
+          getCourseConfigByRewardType().then(res=>{
+              this.RewardType=res.rewardType;
+          })
+      },
     showContentDialog(questionJson){
       // 解析 JSON 字符串为 JavaScript 对象
       // 替换非法换行符等控制字符
@@ -771,7 +790,7 @@ export default {
         this.videoList=[];
         return
       }
-      videoList(row).then(response => {
+        videoListByWatch(row).then(response => {
         this.videoList=response.list
       });
     },

+ 31 - 4
src/views/course/courseWatchLog/index.vue

@@ -56,6 +56,15 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
+        <el-form-item label="企微客户id" prop="nickName" v-if="queryParams.sendType == 2">
+            <el-input
+                v-model="queryParams.qwExternalContactId"
+                placeholder="请输入企微客户id"
+                clearable
+                size="small"
+                @keyup.enter.native="handleQuery"
+            />
+        </el-form-item>
       <!-- 营期名称  periodIds-->
       <el-form-item label="营期名称" prop="periodId">
         <el-select filterable  v-model="queryParams.periodId" placeholder="请选择营期名称"  clearable size="small">
@@ -84,7 +93,11 @@
             :key="dict.dictValue"
             :label="dict.dictLabel"
             :value="dict.dictValue"
-          />
+          >
+              <span style="float: left">{{ dict.dictLabel }}</span>
+              <span style="float: right; color: #8492a6; font-size: 13px">ID: {{ dict.dictValue }}</span>
+              <span v-if="dict.isDel === 1" style="float: right; color: #f56c6c; font-size: 13px; margin-right: 10px">已删除</span>
+          </el-option>
         </el-select>
       </el-form-item>
       <el-form-item v-if="companyName === undefined || companyName === 1" label="所属销售" prop="companyUserId">
@@ -533,7 +546,12 @@
 <!--        <el-table-column label="会员电话" align="center" prop="phone" />-->
 <!--        <el-table-column label="所属销售" align="center" prop="companyUserName" />-->
 <!--        <el-table-column label="所属公司" align="center" prop="companyName" />-->
-        <el-table-column label="转账金额" align="center" prop="amount" />
+<!--        <el-table-column label="转账金额" align="center" prop="amount" />-->
+          <el-table-column :label="RewardType === 1 ? '转账金额' : '积分发放(红包转)'" align="center">
+              <template slot-scope="scope">
+                  <span>{{ RewardType === 1 ? scope.row.amount : scope.row.amount * 1000 }}</span>
+              </template>
+          </el-table-column>
         <el-table-column label="状态" align="center" prop="status" >
           <template slot-scope="scope">
             <el-tag>
@@ -547,6 +565,7 @@
         </el-table-column>
         <el-table-column label="所属企微" align="center" prop="qwUserName" />
         <el-table-column label="创建时间" align="center" prop="createTime" />
+        <el-table-column label="备注" align="center" prop="remark" />
       </el-table>
 
       <pagination
@@ -690,7 +709,7 @@ import {
   updateCourseWatchLog
 } from "@/api/course/courseWatchLog";
 import {listPeriodLabel} from "@/api/course/userCoursePeriod";
-import {courseList, listCourseRedPacketLog, videoList} from '@/api/course/courseRedPacketLog'
+import {courseList, listCourseRedPacketLog, videoListByWatch} from '@/api/course/courseRedPacketLog'
 import {listLogs} from "@/api/course/courseAnswerlogs";
 import {allListTagGroup} from "../../../api/qw/tagGroup";
 import {searchTags} from "../../../api/qw/tag";
@@ -703,6 +722,7 @@ import {getQwList} from "@/api/qw/qwUser";
 import {treeselect} from "@/api/company/companyDept";
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import {getCourseConfigByRewardType} from "../../../api/system/config";
 
 Vue.use(Calendar)
 
@@ -711,6 +731,7 @@ export default {
   components: {Treeselect },
   data() {
     return {
+      RewardType: 1,
       watchTypeList: [
         { dictLabel: 'app', dictValue: 1 },
         { dictLabel: '小程序', dictValue: 2 }
@@ -921,6 +942,7 @@ export default {
     };
   },
   created() {
+    this.getCourseByRewardType();
     courseList().then(response => {
       this.courseLists = response.list;
     });
@@ -945,6 +967,11 @@ export default {
     this.loading=false;
   },
   methods: {
+    getCourseByRewardType(){
+          getCourseConfigByRewardType().then(res=>{
+              this.RewardType=res.rewardType;
+          })
+      },
 
     showContentDialog(questionJson){
       // 解析 JSON 字符串为 JavaScript 对象
@@ -1148,7 +1175,7 @@ export default {
         this.videoList=[];
         return
       }
-      videoList(row).then(response => {
+        videoListByWatch(row).then(response => {
         this.videoList=response.list
       });
     },

+ 9 - 5
src/views/course/courseWatchLog/myCourseWatchLog.vue

@@ -11,10 +11,10 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="客户ID" prop="qwExternalContactId">
+      <el-form-item label="企微客户id" prop="qwExternalContactId">
         <el-input
           v-model="queryParams.qwExternalContactId"
-          placeholder="请输入会员ID"
+          placeholder="请输入企微客户id"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
@@ -64,7 +64,11 @@
             :key="dict.dictValue"
             :label="dict.dictLabel"
             :value="dict.dictValue"
-          />
+          >
+              <span style="float: left">{{ dict.dictLabel }}</span>
+              <span style="float: right; color: #8492a6; font-size: 13px">ID: {{ dict.dictValue }}</span>
+              <span v-if="dict.isDel === 1" style="float: right; color: #f56c6c; font-size: 13px; margin-right: 10px">已删除</span>
+          </el-option>
         </el-select>
       </el-form-item>
       <el-form-item label="创建时间" prop="createTime">
@@ -282,7 +286,7 @@
 
 <script>
 import { myListCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog } from "@/api/course/courseWatchLog";
-import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import { courseList,videoListByWatch } from '@/api/course/courseRedPacketLog'
 import {getMyQwUserList} from "@/api/qw/user";
 import {allListTagGroup} from "../../../api/qw/tagGroup";
 import {searchTags} from "../../../api/qw/tag";
@@ -397,7 +401,7 @@ export default {
         this.videoList=[];
         return
       }
-      videoList(row).then(response => {
+        videoListByWatch(row).then(response => {
         this.videoList=response.list
       });
     },

+ 7 - 3
src/views/course/courseWatchLog/statistics.vue

@@ -18,7 +18,11 @@
             :key="dict.dictValue"
             :label="dict.dictLabel"
             :value="dict.dictValue"
-          />
+          >
+              <span style="float: left">{{ dict.dictLabel }}</span>
+              <span style="float: right; color: #8492a6; font-size: 13px">ID: {{ dict.dictValue }}</span>
+              <span v-if="dict.isDel === 1" style="float: right; color: #f56c6c; font-size: 13px; margin-right: 10px">已删除</span>
+          </el-option>
         </el-select>
       </el-form-item>
       <el-form-item label="销售昵称" prop="nickName">
@@ -66,7 +70,7 @@
 
 <script>
 import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,statisticsList } from "@/api/course/courseWatchLog";
-import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import { courseList,videoListByWatch } from '@/api/course/courseRedPacketLog'
 import PaginationMore from "../../../components/PaginationMore/index.vue";
 import {getDateRange} from "@/utils/common";
 export default {
@@ -148,7 +152,7 @@ export default {
         this.videoList=[];
         return
       }
-      videoList(row).then(response => {
+        videoListByWatch(row).then(response => {
         this.videoList=response.list
       });
     },

+ 25 - 6
src/views/course/courseWatchLog/watchLog.vue

@@ -40,10 +40,10 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="客户ID" prop="qwExternalContactId" v-if="queryParams.sendType == 2">
+      <el-form-item label="企微客户id" prop="qwExternalContactId" v-if="queryParams.sendType == 2">
         <el-input
           v-model="queryParams.qwExternalContactId"
-          placeholder="请输入客户ID"
+          placeholder="请输入企微客户id"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
@@ -105,7 +105,11 @@
             :key="dict.dictValue"
             :label="dict.dictLabel"
             :value="dict.dictValue"
-          />
+          >
+              <span style="float: left">{{ dict.dictLabel }}</span>
+              <span style="float: right; color: #8492a6; font-size: 13px">ID: {{ dict.dictValue }}</span>
+              <span v-if="dict.isDel === 1" style="float: right; color: #f56c6c; font-size: 13px; margin-right: 10px">已删除</span>
+          </el-option>
         </el-select>
       </el-form-item>
 
@@ -618,7 +622,12 @@
         <!--        <el-table-column label="会员电话" align="center" prop="phone" />-->
         <!--        <el-table-column label="所属销售" align="center" prop="companyUserName" />-->
         <!--        <el-table-column label="所属公司" align="center" prop="companyName" />-->
-        <el-table-column label="转账金额" align="center" prop="amount" />
+<!--        <el-table-column label="转账金额" align="center" prop="amount" />-->
+        <el-table-column :label="RewardType === 1 ? '转账金额' : '积分发放(红包转)'" align="center">
+          <template slot-scope="scope">
+              <span>{{ RewardType === 1 ? scope.row.amount : scope.row.amount * 1000 }}</span>
+          </template>
+        </el-table-column>
         <el-table-column label="状态" align="center" prop="status" >
           <template slot-scope="scope">
             <el-tag>
@@ -632,6 +641,7 @@
         </el-table-column>
         <el-table-column label="所属企微" align="center" prop="qwUserName" />
         <el-table-column label="创建时间" align="center" prop="createTime" />
+        <el-table-column label="备注" align="center" prop="remark" />
       </el-table>
 
       <pagination
@@ -814,7 +824,7 @@ import {
   myListCourseWatchLog,
   updateCourseWatchLog
 } from "@/api/course/courseWatchLog";
-import {courseList, myListCourseRedPacketLog, videoList} from '@/api/course/courseRedPacketLog'
+import {courseList, myListCourseRedPacketLog, videoListByWatch} from '@/api/course/courseRedPacketLog'
 import {listPeriodLabel} from "@/api/course/userCoursePeriod";
 import {myListLogs} from "@/api/course/courseAnswerlogs";
 import {getMyQwUserList} from "@/api/qw/user";
@@ -824,6 +834,7 @@ import {allListTagGroup} from "../../../api/qw/tagGroup";
 import Vue from 'vue'
 import Calendar from 'vue-mobile-calendar'
 import {infoSop} from "@/api/qw/sop";
+import {getCourseConfigByRewardType} from "../../../api/system/config";
 
 Vue.use(Calendar)
 
@@ -831,6 +842,7 @@ export default {
   name: "CourseWatchLog",
   data() {
     return {
+      RewardType: 1,
       watchTypeList: [
         { dictLabel: 'app', dictValue: 1 },
         { dictLabel: '小程序', dictValue: 2 }
@@ -1022,6 +1034,7 @@ export default {
     };
   },
   created() {
+    this.getCourseByRewardType();
     courseList().then(response => {
       this.courseLists = response.list;
     });
@@ -1053,6 +1066,12 @@ export default {
 
   },
   methods: {
+      getCourseByRewardType(){
+          getCourseConfigByRewardType().then(res=>{
+              this.RewardType=res.rewardType;
+          })
+      },
+
     copyText(text, event) {
       const clipboard = new ClipboardJS(event.currentTarget, {
         text: () => text,
@@ -1147,7 +1166,7 @@ export default {
         this.videoList = [];
         return
       }
-      videoList(row).then(response => {
+        videoListByWatch(row).then(response => {
         this.videoList = response.list
       });
     },

+ 2 - 2
src/views/course/courseWatchLog/watchLogStatistics.vue

@@ -71,7 +71,7 @@
 <script>
 import { listCourseWatchLog, getCourseWatchLog, delCourseWatchLog, addCourseWatchLog, updateCourseWatchLog, exportCourseWatchLog,watchLogStatistics,watchLogStatisticsExport } from "@/api/course/courseWatchLog";
 import {allList}from "@/api/company/company";
-import { courseList,videoList } from '@/api/course/courseRedPacketLog'
+import { courseList,videoListByWatch } from '@/api/course/courseRedPacketLog'
 import {getDateRange} from "@/utils/common";
 export default {
   name: "CourseWatchLog",
@@ -155,7 +155,7 @@ export default {
         this.videoList=[];
         return
       }
-      videoList(row).then(response => {
+        videoListByWatch(row).then(response => {
         this.videoList=response.list
       });
     },

+ 10 - 0
src/views/crm/components/CustomerSelect.vue

@@ -151,6 +151,13 @@
           </template>
         </el-table-column>
       </el-table>
+      <pagination
+        v-show="total>0"
+        :limit.sync="queryParams.pageSize"
+        :page.sync="queryParams.pageNum"
+        :total="total"
+        @pagination="getList"
+      />
       <div slot="footer" class="dialog-footer" style="width: 100%;display: flex;flex-direction: row-reverse;margin-top: 20px;z-index: 9898989;padding-top: 5px">
         <el-button type="primary" @click="submitForm" style="margin-bottom: 5px">确 定</el-button>
       </div>
@@ -232,6 +239,8 @@ export default {
       open: false,
       // 查询参数
       queryParams: {
+        pageNum: 1,
+        pageSize: 10,
         customerCode: null,
         customerName: null,
         mobile: null,
@@ -421,6 +430,7 @@ export default {
     },
     /** 搜索按钮操作 */
     handleQuery() {
+      this.queryParams.pageNum = 1;
       this.getList();
     },
     /** 重置按钮操作 */

+ 272 - 0
src/views/decoration/componentList.vue

@@ -0,0 +1,272 @@
+<template>
+  <div class="app-container">
+    <!-- 查询区域 -->
+    <el-form :model="queryParams" size="small" inline>
+      <el-form-item label="组件名称">
+        <el-input v-model="queryParams.componentName" placeholder="请输入组件名称" clearable />
+      </el-form-item>
+
+      <el-form-item label="组件类型">
+        <el-select v-model="queryParams.componentTypeCode" placeholder="请选择组件类型" clearable>
+          <el-option
+            v-for="item in typeOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="状态">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
+          <el-option label="启用" :value="1" />
+          <el-option label="停用" :value="0" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" size="mini" @click="getList">查询</el-button>
+        <el-button size="mini" @click="resetQuery">重置</el-button>
+        <el-button type="success" size="mini" @click="handleAdd">新增组件</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 表格区域 -->
+    <el-table :data="componentList" v-loading="loading" border>
+      <el-table-column label="ID" prop="id" width="70" />
+
+      <el-table-column label="缩略图" width="100">
+        <template slot-scope="scope">
+          <img
+            v-if="scope.row.previewImage"
+            :src="scope.row.previewImage"
+            style="width: 70px; height: 45px; object-fit: cover; border-radius: 4px;"
+          />
+          <i
+            v-else
+            :class="scope.row.icon || 'el-icon-picture'"
+            style="font-size: 22px;"
+          />
+        </template>
+      </el-table-column>
+
+      <el-table-column label="组件编码" prop="componentCode" />
+      <el-table-column label="组件名称" prop="componentName" />
+      <el-table-column label="组件类型" prop="componentTypeCode" />
+      <el-table-column label="状态" width="120">
+        <template slot-scope="scope">
+          <el-switch
+            :value="scope.row.status === 1"
+            @change="val => handleChangeStatus(scope.row, val)"
+          />
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" prop="remark" />
+
+      <el-table-column label="操作" width="220">
+        <template slot-scope="scope">
+          <el-button type="text" size="mini" @click="handleEdit(scope.row)">修改</el-button>
+          <el-button type="text" size="mini" style="color:#F56C6C" @click="handleDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 新增/修改弹窗 -->
+    <el-dialog :title="title" :visible.sync="open" width="650px" append-to-body>
+      <el-form :model="form" label-width="100px">
+        <el-form-item label="组件编码">
+          <el-input v-model="form.componentCode" placeholder="请输入组件编码" />
+        </el-form-item>
+
+        <el-form-item label="组件名称">
+          <el-input v-model="form.componentName" placeholder="请输入组件名称" />
+        </el-form-item>
+
+        <el-form-item label="组件类型">
+          <el-select v-model="form.componentTypeCode" placeholder="请选择组件类型" style="width:100%">
+            <el-option
+              v-for="item in typeOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="图标">
+          <el-input v-model="form.icon" placeholder="请输入 icon" />
+        </el-form-item>
+
+        <el-form-item label="缩略图">
+          <el-input v-model="form.previewImage" placeholder="请输入缩略图地址" />
+        </el-form-item>
+
+        <el-form-item label="状态">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="1">启用</el-radio>
+            <el-radio :label="0">停用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item label="备注">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+
+      <div slot="footer">
+        <el-button size="mini" type="primary" @click="submitForm">确 定</el-button>
+        <el-button size="mini" @click="open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  listComponentTypeOptions,
+  listDecorationComponent,
+  getDecorationComponent,
+  saveDecorationComponent,
+  delDecorationComponent,
+  changeDecorationComponentStatus
+} from '@/api/decoration/decoration'
+
+export default {
+  name: 'DecorationComponentList',
+  data() {
+    return {
+      loading: false,
+      open: false,
+      title: '',
+      typeOptions: [],
+      componentList: [],
+      queryParams: {
+        componentName: '',
+        componentTypeCode: '',
+        status: null
+      },
+      form: {
+        id: null,
+        componentCode: '',
+        componentName: '',
+        componentTypeCode: '',
+        icon: '',
+        previewImage: '',
+        status: 1,
+        remark: ''
+      }
+    }
+  },
+  created() {
+    this.getTypeOptions()
+    this.getList()
+  },
+  methods: {
+    /**
+     * 加载组件类型下拉
+     */
+    getTypeOptions() {
+      listComponentTypeOptions().then(res => {
+        this.typeOptions = res.data || []
+      })
+    },
+
+    /**
+     * 查询组件列表
+     */
+    getList() {
+      this.loading = true
+      listDecorationComponent(this.queryParams).then(res => {
+        this.componentList = res.data || []
+        this.loading = false
+      }).catch(() => {
+        this.loading = false
+      })
+    },
+
+    /**
+     * 重置查询条件
+     */
+    resetQuery() {
+      this.queryParams = {
+        componentName: '',
+        componentTypeCode: '',
+        status: null
+      }
+      this.getList()
+    },
+
+    /**
+     * 重置表单
+     */
+    resetForm() {
+      this.form = {
+        id: null,
+        componentCode: '',
+        componentName: '',
+        componentTypeCode: '',
+        icon: '',
+        previewImage: '',
+        status: 1,
+        remark: ''
+      }
+    },
+
+    /**
+     * 新增组件
+     */
+    handleAdd() {
+      this.resetForm()
+      this.title = '新增组件'
+      this.open = true
+    },
+
+    /**
+     * 修改组件
+     */
+    handleEdit(row) {
+      getDecorationComponent(row.id).then(res => {
+        this.form = res.data
+        this.title = '修改组件'
+        this.open = true
+      })
+    },
+
+    /**
+     * 提交表单
+     */
+    submitForm() {
+      saveDecorationComponent(this.form).then(() => {
+        this.$message.success('保存成功')
+        this.open = false
+        this.getList()
+      })
+    },
+
+    /**
+     * 删除组件
+     */
+    handleDelete(row) {
+      this.$confirm('确认删除该组件吗?', '提示', { type: 'warning' }).then(() => {
+        return delDecorationComponent(row.id)
+      }).then(() => {
+        this.$message.success('删除成功')
+        this.getList()
+      })
+    },
+
+    /**
+     * 修改组件状态
+     */
+    handleChangeStatus(row, val) {
+      changeDecorationComponentStatus({
+        id: row.id,
+        status: val ? 1 : 0
+      }).then(() => {
+        this.$message.success('状态修改成功')
+        this.getList()
+      })
+    }
+  }
+}
+</script>

+ 810 - 0
src/views/decoration/templateEditor.vue

@@ -0,0 +1,810 @@
+<template>
+  <div class="app-container template-editor-page">
+    <!-- 顶部标题栏 -->
+    <div class="editor-page-header">
+      <div class="header-left">
+        <div class="page-title">{{ isEdit ? '编辑模板' : '新增模板' }}</div>
+        <div class="page-desc">左侧选择组件,中间预览,右侧操作组件顺序</div>
+      </div>
+
+      <div class="header-right">
+        <el-button size="mini" @click="goBack">返回</el-button>
+        <el-button size="mini" type="primary" @click="submitForm">保存模板</el-button>
+      </div>
+    </div>
+
+    <!-- 顶部表单区域 -->
+    <div class="template-base-form">
+      <el-form :model="form" label-width="90px" size="small">
+        <el-row :gutter="16">
+          <el-col :span="8">
+            <el-form-item label="模板名称">
+              <el-input v-model="form.templateName" placeholder="请输入模板名称" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="8">
+            <el-form-item label="模板类型" prop="templateType">
+               <el-radio-group v-model="form.templateType">
+                   <el-radio label="home">首页模板</el-radio>
+                   <el-radio label="user">个人中心模板</el-radio>
+               </el-radio-group>
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="8">
+            <el-form-item label="模板封面">
+              <el-input v-model="form.coverUrl" placeholder="请输入模板封面地址" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="16">
+          <el-col :span="8">
+            <el-form-item label="状态">
+              <el-radio-group v-model="form.status">
+                <el-radio :label="1">启用</el-radio>
+                <el-radio :label="0">停用</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="16">
+            <el-form-item label="备注">
+              <el-input v-model="form.remark" placeholder="请输入备注" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </div>
+
+    <!-- 编辑器主体 -->
+    <div class="editor-main">
+      <!-- 左侧组件库 -->
+      <div class="component-library">
+        <div class="panel-title">组件库</div>
+
+        <el-select
+          v-model="componentQuery.componentTypeCode"
+          placeholder="组件类型"
+          clearable
+          size="mini"
+          style="width: 100%; margin-bottom: 10px;"
+          @change="loadComponentList"
+        >
+          <el-option
+            v-for="item in typeOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+
+        <div class="component-card-list">
+          <div
+            v-for="item in componentList"
+            :key="item.id"
+            class="component-card"
+            :class="{ disabled: usedComponentCodes.includes(item.componentCode) }"
+            @click="addComponent(item)"
+          >
+            <img
+              v-if="item.previewImage"
+              :src="item.previewImage"
+              class="component-thumb"
+            />
+            <div v-else class="component-thumb icon-thumb">
+              <i :class="item.icon || 'el-icon-picture'"></i>
+            </div>
+            <div class="component-name">{{ item.componentName }}</div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 中间手机画布 -->
+      <div class="canvas-panel">
+        <div class="panel-title">页面画布(手机预览)</div>
+
+        <div class="phone-frame modern-phone">
+          <div class="phone-status-bar modern-status-bar">
+            <span class="status-time">9:41</span>
+            <div class="status-icons">
+              <span class="signal-bars">
+                <i></i><i></i><i></i><i></i>
+              </span>
+              <span class="wifi-icon">◜◝</span>
+              <span class="battery-icon">
+                <span class="battery-body"></span>
+                <span class="battery-cap"></span>
+              </span>
+            </div>
+          </div>
+
+          <div class="phone-screen modern-phone-screen">
+              <draggable
+                  v-model="form.components"
+                  class="canvas-list"
+                  animation="200"
+                  :move="checkMove"
+                  @end="normalizeComponentsOrder"
+              >
+              <transition-group>
+                <div
+                  v-for="(item, index) in form.components"
+                  :key="item.id"
+                  class="canvas-item"
+                  :class="{ active: selectedIndex === index }"
+                  @click.stop="selectComponent(index)"
+                >
+                  <component
+                    :is="getPreviewComponent(item.componentCode)"
+                    class="preview-runtime"
+                  />
+                </div>
+              </transition-group>
+            </draggable>
+
+            <div v-if="!form.components.length" class="empty-tip">
+              请从左侧点击组件添加到页面
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 右侧组件操作 -->
+      <div class="config-panel">
+        <div class="panel-title">组件操作</div>
+
+        <div v-if="selectedComponent" class="config-box">
+          <div class="config-row">
+            <span class="label">组件编码:</span>
+            <span class="value">{{ selectedComponent.componentCode }}</span>
+          </div>
+
+          <div class="config-row">
+            <span class="label">当前位置:</span>
+            <span class="value">第 {{ selectedIndex + 1 }} 个</span>
+          </div>
+
+          <div class="config-actions">
+              <el-button
+                  size="mini"
+                  @click="moveUp"
+                  :disabled="
+                    selectedIndex <= 0 ||
+                    selectedComponent.componentCode === 'Banner' ||
+                    selectedComponent.componentCode === 'RecommendedProducts'"
+              >
+                  上移
+              </el-button>
+              <el-button
+                  size="mini"
+                  @click="moveDown"
+                  :disabled="
+                    selectedIndex >= form.components.length - 1 ||
+                    selectedComponent.componentCode === 'Banner' ||
+                    selectedComponent.componentCode === 'RecommendedProducts'"
+              >
+                  下移
+              </el-button>
+
+            <el-button
+              size="mini"
+              type="danger"
+              @click="removeSelected"
+            >
+              删除组件
+            </el-button>
+          </div>
+        </div>
+
+        <div v-else class="config-empty">
+          请点击中间画布中的组件
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import draggable from 'vuedraggable'
+import {
+  getDecorationTemplate,
+  saveDecorationTemplate,
+  listDecorationComponent,
+  listComponentTypeOptions
+} from '@/api/decoration/decoration'
+
+import BannerPreview from '@/components/decoration/BannerPreview.vue'
+import NoticeBarPreview from '@/components/decoration/NoticeBarPreview.vue'
+import GoodsListPreview from '@/components/decoration/GoodsListPreview.vue'
+import CouponCardPreview from '@/components/decoration/CouponCardPreview.vue'
+import BrandStoryPreview from '@/components/decoration/BrandStoryPreview.vue'
+import BottomCtaPreview from '@/components/decoration/BottomCtaPreview.vue'
+
+export default {
+  name: 'DecorationTemplateEditor',
+  components: {
+    draggable
+  },
+  data() {
+    return {
+      selectedIndex: -1,
+      typeOptions: [],
+      componentList: [],
+      componentQuery: {
+        componentTypeCode: '',
+        status: 1
+      },
+      form: {
+        id: null,
+        templateName: '',
+        templateType: 'home',
+        coverUrl: '',
+        status: 1,
+        remark: '',
+        components: []
+      }
+    }
+  },
+  computed: {
+    isEdit() {
+        return !!this.$route.query.id
+    },
+    selectedComponent() {
+        if (this.selectedIndex < 0) return null
+        return this.form.components[this.selectedIndex] || null
+    },
+    usedComponentCodes() {
+        return this.form.components.map(item => item.componentCode)
+    }
+  },
+  created() {
+    this.loadTypeOptions()
+    this.loadComponentList()
+    this.loadDetail()
+  },
+  methods: {
+    goBack() {
+      this.$router.push('/decoration/template/list')
+    },
+
+    loadTypeOptions() {
+      listComponentTypeOptions().then(res => {
+        this.typeOptions = res.data || []
+      })
+    },
+
+    loadComponentList() {
+      listDecorationComponent(this.componentQuery).then(res => {
+        this.componentList = res.data || []
+      })
+    },
+
+    loadDetail() {
+      const id = this.$route.query.id
+      if (!id) return
+
+      getDecorationTemplate(id).then(res => {
+        const data = res.data || {}
+        const templateData = JSON.parse(data.templateData || '{}')
+
+        this.form = {
+          id: data.id,
+          templateName: data.templateName,
+          templateType: data.templateType,
+          coverUrl: data.coverUrl,
+          status: data.status,
+          remark: data.remark,
+          components: templateData.components || []
+        }
+      })
+    },
+
+    selectComponent(index) {
+      this.selectedIndex = index
+    },
+
+    removeSelected() {
+      if (this.selectedIndex < 0) return
+      this.form.components.splice(this.selectedIndex, 1)
+      this.selectedIndex = -1
+    },
+
+
+    getPreviewComponent(code) {
+      const map = {
+        Banner: BannerPreview,
+        MenuBar: NoticeBarPreview,
+        FlashSale: GoodsListPreview,
+        TodayGroupDeals: CouponCardPreview,
+        LimitedTimeDiscount: BrandStoryPreview,
+        TrendingList: BottomCtaPreview,
+        RecommendedProducts: GoodsListPreview
+      }
+      return map[code] || BannerPreview
+    },
+
+    isFixedTop(componentCode) {
+      return componentCode === 'Banner'
+    },
+
+    isFixedBottom(componentCode) {
+      return componentCode === 'RecommendedProducts'
+    },
+
+    addComponent(item) {
+      // 每个组件只能添加一次
+      if (this.usedComponentCodes.includes(item.componentCode)) {
+          this.$message.warning('该组件已经添加过了,不能重复添加')
+          return
+      }
+
+      this.form.components.push({
+          id: 'cmp_' + Date.now() + '_' + Math.floor(Math.random() * 10000),
+          componentCode: item.componentCode,
+          props: {}
+      })
+
+      this.normalizeComponentsOrder()
+    },
+
+    normalizeComponentsOrder() {
+      const oldSelected = this.selectedComponent ? this.selectedComponent.id : null
+
+      const list = this.form.components || []
+
+      const topList = []
+      const middleList = []
+      const bottomList = []
+
+      list.forEach(item => {
+          if (this.isFixedTop(item.componentCode)) {
+              topList.push(item)
+          } else if (this.isFixedBottom(item.componentCode)) {
+              bottomList.push(item)
+          } else {
+              middleList.push(item)
+          }
+      })
+
+      this.form.components = topList.concat(middleList, bottomList)
+
+      if (oldSelected) {
+          this.selectedIndex = this.form.components.findIndex(item => item.id === oldSelected)
+      }
+    },
+
+    checkMove(evt) {
+      const dragged = evt.draggedContext && evt.draggedContext.element
+      const related = evt.relatedContext && evt.relatedContext.element
+
+      if (!dragged) return true
+
+      // Banner 不允许被拖动到非顶部
+      if (this.isFixedTop(dragged.componentCode)) {
+          return false
+      }
+
+      // RecommendedProducts 不允许被拖动到非底部
+      if (this.isFixedBottom(dragged.componentCode)) {
+          return false
+      }
+
+      // 其他组件不能拖到 Banner 上面
+      if (related && this.isFixedTop(related.componentCode)) {
+          return false
+      }
+
+      // 其他组件不能拖到 RecommendedProducts 下面
+      if (related && this.isFixedBottom(related.componentCode)) {
+          return false
+      }
+
+      return true
+    },
+
+    moveUp() {
+      if (this.selectedIndex <= 0) return
+
+      const current = this.form.components[this.selectedIndex]
+      const prev = this.form.components[this.selectedIndex - 1]
+
+      if (this.isFixedTop(current.componentCode)) {
+          this.$message.warning('轮播图必须在最上面')
+          return
+      }
+
+      if (prev && this.isFixedTop(prev.componentCode)) {
+          this.$message.warning('不能移动到轮播图上方')
+          return
+      }
+
+      const arr = this.form.components
+      this.$set(arr, this.selectedIndex, prev)
+      this.$set(arr, this.selectedIndex - 1, current)
+      this.selectedIndex = this.selectedIndex - 1
+
+      this.normalizeComponentsOrder()
+    },
+
+    moveDown() {
+      if (this.selectedIndex < 0 || this.selectedIndex >= this.form.components.length - 1) return
+
+      const current = this.form.components[this.selectedIndex]
+      const next = this.form.components[this.selectedIndex + 1]
+
+      if (this.isFixedBottom(current.componentCode)) {
+          this.$message.warning('推荐商品必须在最下面')
+          return
+      }
+
+      if (next && this.isFixedBottom(next.componentCode)) {
+          this.$message.warning('不能移动到推荐商品下方')
+          return
+      }
+
+      const arr = this.form.components
+      this.$set(arr, this.selectedIndex, next)
+      this.$set(arr, this.selectedIndex + 1, current)
+      this.selectedIndex = this.selectedIndex + 1
+
+      this.normalizeComponentsOrder()
+    },
+
+    submitForm() {
+      // 保存前再兜底整理一次顺序
+      this.normalizeComponentsOrder()
+
+      const payload = {
+          id: this.form.id,
+          templateName: this.form.templateName,
+          templateType: this.form.templateType,
+          coverUrl: this.form.coverUrl,
+          status: this.form.status,
+          remark: this.form.remark,
+          templateData: JSON.stringify({
+              pageConfig: {
+                  templateName: this.form.templateName
+              },
+              components: this.form.components
+          })
+      }
+
+      saveDecorationTemplate(payload).then(() => {
+          this.$message.success('保存成功')
+          this.goBack()
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.template-editor-page {
+  height: calc(100vh - 84px);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+/* 页面顶部 */
+.editor-page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+  flex-shrink: 0;
+}
+
+.page-title {
+  font-size: 20px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.page-desc {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 4px;
+}
+
+/* 顶部表单 */
+.template-base-form {
+  background: #fff;
+  padding: 16px 16px 2px;
+  border-radius: 8px;
+  margin-bottom: 16px;
+  flex-shrink: 0;
+}
+
+/* 主体三栏 */
+.editor-main {
+  flex: 1;
+  display: flex;
+  gap: 16px;
+  min-height: 0;
+  overflow: hidden;
+}
+
+/* 左侧组件库 */
+.component-library {
+  width: 280px;
+  border: 1px solid #ebeef5;
+  padding: 12px;
+  overflow-y: auto;
+  background: #fff;
+  border-radius: 8px;
+  flex-shrink: 0;
+}
+
+/* 中间画布 */
+.canvas-panel {
+  flex: 1;
+  padding: 12px;
+  background: #f5f7fa;
+  overflow: auto;
+  min-width: 0;
+  border-radius: 8px;
+}
+
+/* 右侧操作区 */
+.config-panel {
+  width: 240px;
+  border: 1px solid #ebeef5;
+  padding: 12px;
+  background: #fff;
+  overflow-y: auto;
+  border-radius: 8px;
+  flex-shrink: 0;
+}
+
+.panel-title {
+  font-size: 15px;
+  font-weight: bold;
+  margin-bottom: 12px;
+}
+
+/* 左侧组件卡片 */
+.component-card-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+
+.component-card {
+  width: calc(50% - 5px);
+  border: 1px solid #dcdfe6;
+  border-radius: 6px;
+  padding: 8px;
+  cursor: pointer;
+  background: #fff;
+  transition: all 0.2s;
+}
+
+.component-card:hover {
+  border-color: #409EFF;
+  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
+}
+
+.component-thumb {
+  width: 100%;
+  height: 70px;
+  object-fit: cover;
+  border-radius: 4px;
+}
+
+.icon-thumb {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 26px;
+  background: #f5f7fa;
+}
+
+.component-name {
+  margin-top: 8px;
+  text-align: center;
+  font-size: 13px;
+}
+
+/* 手机 */
+.phone-frame.modern-phone {
+  width: 370px;
+  height: 760px;
+  margin: 0 auto;
+  background: #0f0f10;
+  border-radius: 42px;
+  padding: 10px;
+  box-sizing: border-box;
+  box-shadow:
+    0 18px 40px rgba(0, 0, 0, 0.18),
+    0 2px 8px rgba(0, 0, 0, 0.08);
+  position: relative;
+  border: 2px solid #1b1b1d;
+}
+
+.modern-status-bar {
+  height: 28px;
+  padding: 0 16px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  color: #111;
+  font-size: 12px;
+  font-weight: 600;
+  background: #fff;
+  border-top-left-radius: 32px;
+  border-top-right-radius: 32px;
+  box-sizing: border-box;
+}
+
+.status-time {
+  color: #222;
+  font-size: 12px;
+  font-weight: 600;
+}
+
+.status-icons {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.signal-bars {
+  display: flex;
+  align-items: flex-end;
+  gap: 2px;
+  height: 12px;
+}
+
+.signal-bars i {
+  display: block;
+  width: 2px;
+  background: #222;
+  border-radius: 1px;
+}
+
+.signal-bars i:nth-child(1) { height: 4px; }
+.signal-bars i:nth-child(2) { height: 6px; }
+.signal-bars i:nth-child(3) { height: 8px; }
+.signal-bars i:nth-child(4) { height: 10px; }
+
+.wifi-icon {
+  font-size: 10px;
+  color: #222;
+  line-height: 1;
+}
+
+.battery-icon {
+  display: flex;
+  align-items: center;
+  gap: 2px;
+}
+
+.battery-body {
+  width: 18px;
+  height: 9px;
+  border: 1.5px solid #222;
+  border-radius: 2px;
+  position: relative;
+  display: inline-block;
+}
+
+.battery-body::after {
+  content: '';
+  position: absolute;
+  left: 1px;
+  top: 1px;
+  width: 11px;
+  height: 5px;
+  background: #9adf7f;
+  border-radius: 1px;
+}
+
+.battery-cap {
+  width: 2px;
+  height: 5px;
+  background: #222;
+  border-radius: 1px;
+  display: inline-block;
+}
+
+.modern-phone-screen {
+  height: calc(100% - 28px);
+  background: #fff;
+  border-bottom-left-radius: 32px;
+  border-bottom-right-radius: 32px;
+  overflow-y: auto;
+  padding: 10px 10px 14px;
+  box-sizing: border-box;
+}
+
+.modern-phone-screen::-webkit-scrollbar {
+  width: 4px;
+}
+
+.modern-phone-screen::-webkit-scrollbar-thumb {
+  background: rgba(0, 0, 0, 0.15);
+  border-radius: 2px;
+}
+
+/* 画布组件 */
+.canvas-list {
+  min-height: 200px;
+}
+
+.canvas-item {
+  background: #fff;
+  border: 1px solid #ebeef5;
+  border-radius: 12px;
+  padding: 10px;
+  margin-bottom: 10px;
+  cursor: move;
+  transition: all 0.2s;
+}
+
+.canvas-item:hover {
+  border-color: #409EFF;
+  box-shadow: 0 2px 10px rgba(64, 158, 255, 0.12);
+}
+
+.canvas-item.active {
+  border-color: #409EFF;
+  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.12);
+}
+
+.preview-runtime {
+  width: 100%;
+}
+
+.empty-tip {
+  text-align: center;
+  color: #909399;
+  padding: 40px 0;
+}
+
+/* 右侧操作面板 */
+.config-box {
+  border: 1px solid #ebeef5;
+  border-radius: 8px;
+  padding: 12px;
+  background: #fafafa;
+}
+
+.config-row {
+  margin-bottom: 12px;
+  line-height: 20px;
+}
+
+.config-row .label {
+  color: #909399;
+}
+
+.config-row .value {
+  color: #303133;
+  word-break: break-all;
+}
+
+.config-actions {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.config-empty {
+  color: #909399;
+  text-align: center;
+  padding: 40px 10px;
+}
+
+.component-card.disabled {
+    opacity: 0.45;
+    cursor: not-allowed;
+}
+
+.component-card.disabled:hover {
+    border-color: #dcdfe6;
+    box-shadow: none;
+}
+</style>

+ 187 - 0
src/views/decoration/templateList.vue

@@ -0,0 +1,187 @@
+<template>
+  <div class="app-container">
+    <el-button
+      type="success"
+      size="mini"
+      @click="handleAdd"
+      style="margin-bottom: 10px;"
+    >
+      新增模板
+    </el-button>
+
+    <el-table :data="templateList" v-loading="loading" border>
+      <el-table-column label="ID" prop="id" width="70" />
+      <el-table-column label="模板名称" prop="templateName" />
+      <el-table-column label="模板类型">
+        <template slot-scope="scope">
+            <el-tag v-if="scope.row.templateType === 'home'">
+                首页模板
+            </el-tag>
+
+            <el-tag
+                type="success"
+                v-else-if="scope.row.templateType === 'user'"
+            >
+                个人中心
+            </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" width="100">
+        <template slot-scope="scope">
+          {{ scope.row.status === 1 ? '启用' : '停用' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" prop="remark" />
+      <el-table-column label="操作" width="340">
+        <template slot-scope="scope">
+          <el-button type="text" size="mini" @click="handleEdit(scope.row)"> 编辑 </el-button>
+
+          <el-button type="text" size="mini" @click="handlePreview(scope.row)"> 查看JSON </el-button>
+
+          <el-tag v-if="currentTemplateMap[scope.row.templateType] === scope.row.id" type="success" size="mini"> 使用中 </el-tag>
+
+          <el-button v-else type="text" size="mini" @click="handleUseTemplate(scope.row)"> 使用当前模板 </el-button>
+
+          <el-button type="text" size="mini" style="color:#F56C6C" @click="handleDelete(scope.row)"> 删除 </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- ✅ JSON预览弹窗(必须放在同一个根div里) -->
+    <el-dialog
+      title="模板JSON"
+      :visible.sync="jsonOpen"
+      width="80%"
+      top="5vh"
+    >
+      <pre class="json-preview">{{ currentJson }}</pre>
+
+      <div slot="footer">
+        <el-button size="mini" @click="jsonOpen = false">关 闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  listDecorationTemplate,
+  getDecorationTemplate,
+  delDecorationTemplate,
+  useDecorationTemplate,
+  getCurrentCompanyTemplate
+} from '@/api/decoration/decoration'
+
+export default {
+  name: 'DecorationTemplateList',
+  data() {
+    return {
+      loading: false,
+      templateList: [],
+      jsonOpen: false,
+      currentJson: '',
+      currentTemplateMap: {}
+    }
+  },
+  created() {
+    this.getList()
+    this.loadCurrentTemplates()
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      listDecorationTemplate()
+        .then(res => {
+          this.templateList = res.data || []
+        })
+        .finally(() => {
+          this.loading = false
+        })
+    },
+
+    handleAdd() {
+      this.$router.push({
+        path: '/decoration/template/editor'
+      })
+    },
+
+    handleEdit(row) {
+      this.$router.push({
+        path: '/decoration/template/editor',
+        query: { id: row.id }
+      })
+    },
+
+    handlePreview(row) {
+      getDecorationTemplate(row.id).then(res => {
+        const data = res.data || {}
+
+        try {
+          this.currentJson = JSON.stringify(
+            JSON.parse(data.templateData || '{}'),
+            null,
+            2
+          )
+        } catch (e) {
+          this.currentJson = data.templateData || ''
+        }
+
+        this.jsonOpen = true
+      })
+    },
+
+    handleDelete(row) {
+      this.$confirm('确认删除该模板吗?', '提示', { type: 'warning' })
+        .then(() => delDecorationTemplate(row.id))
+        .then(() => {
+          this.$message.success('删除成功')
+          this.getList()
+        })
+    },
+
+    // 查询当前公司每种类型正在使用的模板
+    loadCurrentTemplates() {
+      const types = ['home', 'activity', 'user']
+
+      types.forEach(type => {
+        getCurrentCompanyTemplate({
+          templateType: type
+        }).then(res => {
+          this.$set(this.currentTemplateMap, type, res.data)
+        })
+      })
+    },
+
+// 使用当前模板
+    handleUseTemplate(row) {
+      this.$confirm('确认使用该模板吗?同类型模板会被替换。', '提示', {
+        type: 'warning'
+      }).then(() => {
+        return useDecorationTemplate({
+          templateId: row.id
+        })
+      }).then(() => {
+        this.$message.success('使用成功')
+        this.loadCurrentTemplates()
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.json-preview {
+  background: #1e1e1e;
+  color: #dcdcdc;
+  padding: 16px;
+  border-radius: 6px;
+  font-size: 13px;
+  line-height: 1.6;
+
+  max-height: 70vh;
+  overflow: auto;
+
+  white-space: pre-wrap;
+  word-break: break-all;
+}
+</style>

+ 393 - 0
src/views/fastGpt/fastGptChatQuestion/index.vue

@@ -0,0 +1,393 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="会话ID" prop="sessionId">
+        <el-input
+          v-model="queryParams.sessionId"
+          placeholder="请输入会话ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="消息ID" prop="msgId">
+        <el-input
+          v-model="queryParams.msgId"
+          placeholder="请输入消息ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="外部ID" prop="extId">
+        <el-input
+          v-model="queryParams.extId"
+          placeholder="请输入外部ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="用户id" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入用户id"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="公司ID" prop="companyId">
+        <el-input
+          v-model="queryParams.companyId"
+          placeholder="请输入公司ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="销售ID" prop="companyUserId">
+        <el-input
+          v-model="queryParams.companyUserId"
+          placeholder="请输入销售ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="角色ID" prop="roleId">
+        <el-input
+          v-model="queryParams.roleId"
+          placeholder="请输入角色ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="用户类型 1微信用户 2小程序用户 3销售用户" prop="userType">
+        <el-select v-model="queryParams.userType" placeholder="请选择用户类型 1微信用户 2小程序用户 3销售用户" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </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:fastgptChatQuestion: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:fastgptChatQuestion: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:fastgptChatQuestion: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:fastgptChatQuestion:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="fastgptChatQuestionList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="主键id" align="center" prop="id" />
+      <el-table-column label="会话id" align="center" prop="sessionId" />
+      <el-table-column label="消息id" align="center" prop="msgId" />
+      <el-table-column label="外部ID" align="center" prop="extId" />
+      <el-table-column label="用户id" align="center" prop="userId" />
+      <el-table-column label="公司ID" align="center" prop="companyId" />
+      <el-table-column label="销售ID" align="center" prop="companyUserId" />
+      <el-table-column label="角色ID" align="center" prop="roleId" />
+      <el-table-column label="昵称" align="center" prop="nickName" />
+      <el-table-column label="用户类型 1微信用户 2小程序用户 3销售用户" align="center" prop="userType" />
+      <el-table-column label="客户内容" align="center" prop="userContent" />
+      <el-table-column label="销售回复内容" align="center" prop="companyUserContent" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['fastGpt:fastgptChatQuestion:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['fastGpt:fastgptChatQuestion:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改聊天问题收集对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="会话id" prop="sessionId">
+          <el-input v-model="form.sessionId" placeholder="请输入会话id" />
+        </el-form-item>
+        <el-form-item label="消息id" prop="msgId">
+          <el-input v-model="form.msgId" placeholder="请输入消息id" />
+        </el-form-item>
+        <el-form-item label="外部ID" prop="extId">
+          <el-input v-model="form.extId" placeholder="请输入外部ID" />
+        </el-form-item>
+        <el-form-item label="用户id" prop="userId">
+          <el-input v-model="form.userId" placeholder="请输入用户id" />
+        </el-form-item>
+        <el-form-item label="公司ID" prop="companyId">
+          <el-input v-model="form.companyId" placeholder="请输入公司ID" />
+        </el-form-item>
+        <el-form-item label="销售ID" prop="companyUserId">
+          <el-input v-model="form.companyUserId" placeholder="请输入销售ID" />
+        </el-form-item>
+        <el-form-item label="角色ID" prop="roleId">
+          <el-input v-model="form.roleId" placeholder="请输入角色ID" />
+        </el-form-item>
+        <el-form-item label="昵称" prop="nickName">
+          <el-input v-model="form.nickName" 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="客户内容">
+          <editor v-model="form.userContent" :min-height="192"/>
+        </el-form-item>
+        <el-form-item label="销售回复内容">
+          <editor v-model="form.companyUserContent" :min-height="192"/>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listFastgptChatQuestion, getFastgptChatQuestion, delFastgptChatQuestion, addFastgptChatQuestion, updateFastgptChatQuestion, exportFastgptChatQuestion } from "@/api/fastGpt/fastgptChatQuestion";
+
+export default {
+  name: "fastGptChatQuestion",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 聊天问题收集表格数据
+      fastgptChatQuestionList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        sessionId: null,
+        msgId: null,
+        extId: null,
+        userId: null,
+        companyId: null,
+        companyUserId: null,
+        roleId: null,
+        nickName: null,
+        userType: null,
+        userContent: null,
+        companyUserContent: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询聊天问题收集列表 */
+    getList() {
+      this.loading = true;
+      listFastgptChatQuestion(this.queryParams).then(response => {
+        this.fastgptChatQuestionList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        sessionId: null,
+        msgId: null,
+        extId: null,
+        userId: null,
+        companyId: null,
+        companyUserId: null,
+        roleId: null,
+        nickName: null,
+        userType: null,
+        userContent: null,
+        companyUserContent: null,
+        createTime: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加聊天问题收集";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getFastgptChatQuestion(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改聊天问题收集";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateFastgptChatQuestion(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addFastgptChatQuestion(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除聊天问题收集编号为"' + ids + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delFastgptChatQuestion(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有聊天问题收集数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportFastgptChatQuestion(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 220 - 0
src/views/fastGpt/fastGptChatQuestionStatistics/index.vue

@@ -0,0 +1,220 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="频次" prop="frequency">
+        <el-input
+          v-model="queryParams.frequency"
+          placeholder="请输入频次"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="是否解决" prop="isResolve">
+        <el-select v-model="queryParams.isResolve" placeholder="请选择" clearable size="small" style="width: 180px">
+          <el-option label="是" :value="1" />
+          <el-option label="否" :value="0" />
+        </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">
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="fastGptChatQuestionStatisticsList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="id" width="80" />
+      <el-table-column label="所属类别" align="center" prop="questionCategory" />
+<!--      <el-table-column label="来源" align="center" prop="source" />-->
+      <el-table-column label="内容摘要" align="center" prop="contentSummary" />
+      <el-table-column label="频次" align="center" prop="frequency" width="90" />
+      <el-table-column label="是否解决" align="center" prop="isResolve" width="100">
+        <template slot-scope="scope">
+          <span>{{ scope.row.isResolve === 1 ? '是' : (scope.row.isResolve === 0 ? '否' : '-') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="170" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-document"
+            @click="handleDetail(scope.row)"
+          >详情</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['fastGpt:fastGptChatQuestionStatistics: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="detailTitle" :visible.sync="detailOpen" width="1100px" append-to-body>
+      <el-table border v-loading="detailLoading" :data="detailList">
+<!--        <el-table-column label="分类内容摘要" prop="user_content" min-width="160" />-->
+        <el-table-column label="所属公司" prop="companyName" min-width="140" />
+        <el-table-column label="所属销售" prop="companyUserNickName" min-width="120" />
+        <el-table-column label="客户昵称" prop="nickName" min-width="120" />
+        <el-table-column label="客户内容" prop="userContent" min-width="180" />
+        <el-table-column label="销售回复内容" prop="companyUserContent" min-width="180" />
+        <el-table-column label="创建时间" prop="createTime" width="170" />
+        <el-table-column label="操作" width="110" align="center">
+          <template slot-scope="scope">
+            <el-button size="mini" type="text" @click="handleSalesReply(scope.row)">销售回复</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-show="detailTotal > 0"
+        :total="detailTotal"
+        :page.sync="detailQuery.pageNum"
+        :limit.sync="detailQuery.pageSize"
+        @pagination="getDetailList"
+      />
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="detailOpen = false">关 闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listFastGptChatQuestionStatistics, delFastGptChatQuestionStatistics, listFastGptChatQuestionStatisticsDetail, saveFastGptChatQuestionStatisticsSalesReply } from "@/api/fastGpt/fastGptChatQuestionStatistics";
+
+export default {
+  name: "fastGptChatQuestionStatistics",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 高频聊天问题统计表格数据
+      fastGptChatQuestionStatisticsList: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        frequency: null,
+        isResolve: null,
+      },
+
+      detailOpen: false,
+      detailTitle: "问题明细",
+      detailLoading: false,
+      detailTotal: 0,
+      detailQuery: {
+        questionStatisticsId: null,
+        pageNum: 1,
+        pageSize: 10
+      },
+      detailList: []
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询高频聊天问题统计列表 */
+    getList() {
+      this.loading = true;
+      listFastGptChatQuestionStatistics(this.queryParams).then(response => {
+        this.fastGptChatQuestionStatisticsList = 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.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除高频聊天问题统计编号为"' + ids + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delFastGptChatQuestionStatistics(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+
+    handleDetail(row) {
+      this.detailQuery.questionStatisticsId = row.id || null;
+      this.detailQuery.pageNum = 1;
+      this.detailQuery.pageSize = 10;
+      this.detailOpen = true;
+      this.detailTitle = `问题明细`;
+      this.getDetailList();
+    },
+    getDetailList() {
+      this.detailLoading = true;
+      listFastGptChatQuestionStatisticsDetail(this.detailQuery).then(res => {
+        this.detailList = res.rows || res.data || [];
+        this.detailTotal = res.total != null ? res.total : 0;
+      }).finally(() => {
+        this.detailLoading = false;
+      });
+    },
+    handleSalesReply(detailRow) {
+      this.$prompt('请输入销售回复内容', '销售回复', {
+        confirmButtonText: '保存',
+        cancelButtonText: '取消',
+        inputType: 'textarea',
+        inputValue: detailRow.companyUserContent || ''
+      }).then(({ value }) => {
+        return saveFastGptChatQuestionStatisticsSalesReply({
+          id: detailRow.id,
+          companyUserContent: value,
+          questionStatisticsId:detailRow.questionStatisticsId
+        });
+      }).then(() => {
+        this.msgSuccess("保存成功");
+        this.getDetailList();
+      }).catch(() => {});
+    }
+  }
+};
+</script>

+ 22 - 0
src/views/hisStore/components/productOrder.vue

@@ -126,6 +126,20 @@
                   {{ order.deliveryId }}
                 </span>
           </el-descriptions-item>
+          <el-descriptions-item label="物流状态">
+                <span v-if="order!=null">
+                  <el-tag v-for="(item, index) in deliveryStatusOptions"
+                          v-if="order.deliveryStatus==item.dictValue"
+                          :key="index">{{ item.dictLabel }}</el-tag>
+                </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="物流跟踪状态">
+                <span v-if="order!=null">
+                  <el-tag v-for="(item, index) in deliveryTypeOptions"
+                          v-if="order.deliveryType==item.dictValue"
+                          :key="index">{{ item.dictLabel }}</el-tag>
+                </span>
+          </el-descriptions-item>
           <el-descriptions-item label="档期归属">
             <el-tag prop="scheduleId" v-for="(item, index) in scheduleOptions"
                     v-if="order!=null&&order.scheduleId==item.id">{{ item.name }}
@@ -1234,6 +1248,8 @@ export default {
       },
       editRules: {},
       createTypeOptions: [],
+      deliveryStatusOptions: [],
+      deliveryTypeOptions: [],
       orderTypeOptions: [],
       payTypeOptions: [],
       statusOptions: [],
@@ -1284,6 +1300,12 @@ export default {
     };
   },
   created() {
+    this.getDicts("store_order_delivery_status").then((response) => {
+      this.deliveryStatusOptions = response.data;
+    });
+    this.getDicts("store_order_delivery_type").then((response) => {
+      this.deliveryTypeOptions = response.data;
+    });
     this.getDicts("crm_customer_user_status").then((response) => {
       this.customerUserStatusOptions = response.data;
     });

+ 2 - 0
src/views/hisStore/storeOrder/healthStoreList.vue

@@ -452,6 +452,8 @@
       </el-table-column>
       <el-table-column label="ERP电话" align="center" prop="erpPhone" width="120px" v-if="SFDFopen"/>
       <el-table-column label="ERP账号" align="center" prop="erpAccount" width="120px" v-if="SFDFopen"/>
+      <el-table-column label="所属公司" align="center" prop="companyName" min-width="140" show-overflow-tooltip />
+      <el-table-column label="所属员工" align="center" prop="companyUserNickName" width="120" show-overflow-tooltip />
       <el-table-column label="小程序名称" align="center" prop="miniProgramName"/>
       <el-table-column align="center" label="用户昵称" prop="nickname" width="150px">
         <template slot-scope="scope">

+ 18 - 18
src/views/hisStore/storeOrder/index.vue

@@ -306,24 +306,24 @@
           @click="openDeliveryNote"
         >批量导入物流单号</el-button>
       </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="warning"
-          icon="el-icon-download"
-          size="mini"
-          @click="handleExportDetails"
-          v-hasPermi="['store:storeOrder:export:details']"
-        >导出订单(明文)</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="warning"
-          icon="el-icon-download"
-          size="mini"
-          @click="handleExportItemsDetails"
-          v-hasPermi="['store:storeOrder:exportItems:details']"
-        >导出订单明细(明文)</el-button>
-      </el-col>
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="warning"-->
+<!--          icon="el-icon-download"-->
+<!--          size="mini"-->
+<!--          @click="handleExportDetails"-->
+<!--          v-hasPermi="['store:storeOrder:export:details']"-->
+<!--        >导出订单(明文)</el-button>-->
+<!--      </el-col>-->
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="warning"-->
+<!--          icon="el-icon-download"-->
+<!--          size="mini"-->
+<!--          @click="handleExportItemsDetails"-->
+<!--          v-hasPermi="['store:storeOrder:exportItems:details']"-->
+<!--        >导出订单明细(明文)</el-button>-->
+<!--      </el-col>-->
       <el-col :span="1.5">
         <el-button
           type="danger"

+ 9 - 1
src/views/qw/externalContact/index.vue

@@ -239,6 +239,12 @@
                         placeholder="选择删除时间">
         </el-date-picker>
       </el-form-item>
+      <el-form-item label="是否下载APP" prop="isDownloadApp">
+        <el-select v-model="queryParams.isDownloadApp" placeholder="请选择是否下载app" clearable size="small">
+          <el-option label="是" :value="1" />
+          <el-option label="否" :value="0" />
+        </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>
@@ -1252,7 +1258,9 @@ export default {
         wayId:null,
         levelType:null,
         companyUser:null,
-        userRepeat: null
+        userRepeat: null,
+        delTime: null,
+        isDownloadApp: null
       },
       //选择的标签
       selectTags:[],

+ 2 - 2
src/views/qw/friendWelcome/indexNew.vue

@@ -977,8 +977,8 @@ export default {
         this.fileFrom.miniprogramTitle = item.miniprogram.title;
         this.fileFrom.miniprogramPicUrl = "待生成";
         this.fileFrom.miniprogramPage = "待生成";
-        this.fileFrom.videoId = item.miniprogram.videoId;
-        this.fileFrom.courseId = item.miniprogram.courseId;
+        this.fileFrom.videoId = item.miniprogram.videoId ? parseInt(item.miniprogram.videoId) : null
+        this.fileFrom.courseId = item.miniprogram.courseId ? parseInt(item.miniprogram.courseId) : null
         this.fileFrom.expiresDays = item.miniprogram.expiresDays;
 
         videoList(item.miniprogram.courseId).then(response => {

+ 11 - 0
src/views/qw/sop/addSop.vue

@@ -299,6 +299,15 @@
           </el-date-picker>
           <Tip title="SOP开始发送时间" />
         </el-form-item>
+          <el-form-item label="截至时间" prop="deadLineTime" v-if="form.sendType==4">
+              <el-date-picker clearable size="small"
+                              v-model="form.deadLineTime"
+                              type="datetime"
+                              value-format="yyyy-MM-dd HH:mm:ss"
+                              placeholder="选择截至时间">
+              </el-date-picker>
+              <Tip title="仅用于SOP新客对话,此sop任务什么时候截至,到截止时间之后,相当于这个sop任务作废,任务不再生效," />
+          </el-form-item>
         <el-form-item label="任务过期时间" prop="expiryTime">
           <el-row>
             <el-input-number v-model="form.expiryTime" :min="1" :max="100"></el-input-number>
@@ -600,6 +609,7 @@ export default {
         filterType: 2,
         expiryTime: 4,
         isAutoSop: 1,
+        deadLineTime: null,
         autoSopTime: {autoSopType: 2, autoStartTime: '00:00', autoEndTime: '24:00', autoSopSend: 2},
       },
       userSelectList: [],
@@ -954,6 +964,7 @@ export default {
         createBy: null,
         createTime: null,
         isRating: null,
+        deadLineTime : null,
         autoSopTime: {autoSopType: 2, autoStartTime: '00:00', autoEndTime: '24:00', autoSopSend: 2},
         openCommentStatus: 0,
       };

+ 6 - 1
src/views/qw/sop/deptSop.vue

@@ -919,6 +919,7 @@ export default {
   components: {CustomerGroupDetails, qwUserList, ImageUpload, sopLogsDetails, SendMsgSopOpenTool},
   data() {
     return {
+      projectFrom:process.env.VUE_APP_PROJECT_FROM,
       // 存储每一行的展开状态
       expandedRows: {},
       //模板查询
@@ -1618,7 +1619,11 @@ export default {
      * 查看模板
      */
     handleQueryDetails(row) {
-      this.$router.push(`/qw/sopTemp/updateSopTemp/${row.tempId}/3`)
+        if (this.projectFrom ==='sxjz'){
+            this.$router.push(`/qw/sopTemp/updateSopTempSXJZ/${row.tempId}/3`)
+        }else {
+            this.$router.push(`/qw/sopTemp/updateSopTemp/${row.tempId}/3`)
+        }
     },
     /** 提交按钮 */
     submitForm() {

+ 6 - 1
src/views/qw/sop/mySop.vue

@@ -918,6 +918,7 @@ export default {
   components: {CustomerGroupDetails, qwUserList, ImageUpload, sopLogsDetails, SendMsgSopOpenTool},
   data() {
     return {
+      projectFrom:process.env.VUE_APP_PROJECT_FROM,
       // 存储每一行的展开状态
       expandedRows: {},
       //模板查询
@@ -1617,7 +1618,11 @@ export default {
      * 查看模板
      */
     handleQueryDetails(row) {
-      this.$router.push(`/qw/sopTemp/updateSopTemp/${row.tempId}/3`)
+        if (this.projectFrom ==='sxjz'){
+            this.$router.push(`/qw/sopTemp/updateSopTempSXJZ/${row.tempId}/3`)
+        }else {
+            this.$router.push(`/qw/sopTemp/updateSopTemp/${row.tempId}/3`)
+        }
     },
     /** 提交按钮 */
     submitForm() {

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

@@ -961,6 +961,7 @@ export default {
   components: {CustomerGroupDetails, qwUserList, ImageUpload, sopLogsDetails, SendMsgSopOpenTool},
   data() {
     return {
+      projectFrom:process.env.VUE_APP_PROJECT_FROM,
       // 存储每一行的展开状态
       expandedRows: {},
       //模板查询
@@ -1669,7 +1670,13 @@ export default {
      * 查看模板
      */
     handleQueryDetails(row) {
-      this.$router.push(`/qw/sopTemp/updateSopTemp/${row.tempId}/3`)
+
+        if (this.projectFrom ==='sxjz'){
+            this.$router.push(`/qw/sopTemp/updateSopTempSXJZ/${row.tempId}/3`)
+        }else {
+            this.$router.push(`/qw/sopTemp/updateSopTemp/${row.tempId}/3`)
+        }
+
     },
     /** 提交按钮 */
     submitForm() {

+ 9 - 0
src/views/qw/sop/updateSop.vue

@@ -229,6 +229,15 @@
                           placeholder="选择开始时间">
           </el-date-picker>
         </el-form-item>
+          <el-form-item label="截至时间" prop="deadLineTime" v-if="form.sendType==4">
+              <el-date-picker clearable size="small"
+                              v-model="form.deadLineTime"
+                              type="datetime"
+                              value-format="yyyy-MM-dd HH:mm:ss"
+                              placeholder="选择截至时间">
+              </el-date-picker>
+              <Tip title="仅用于SOP新客对话,此sop任务什么时候截至,到截止时间之后,相当于这个sop任务作废,任务不再生效,(不填永久生效,生成所有天数的新客对话)" />
+          </el-form-item>
 
         <el-form-item label="是否只发送注册用户" prop="isRegister" v-if="form.type != 3">
           <el-radio-group v-model="form.isRegister">

+ 19 - 4
src/views/qw/sopLogs/sopLogsList.vue

@@ -173,11 +173,16 @@
           <el-card class="box-card" style="margin-top: 2%">
             <div slot="header" class="clearfix" style="display: flex;justify-content: space-between;align-items: center;">
               <div>
-                <span>类型:</span>
+                  <span v-if="item.onlyUnregistered">发送用户类型:{{ getOnlyUnregisteredText(item.onlyUnregistered) }}</span>
+              </div>
+              <div>
+                <span>消息类型:</span>
                 <span v-if="item.contentType == 1">文本</span>
                 <span v-if="item.contentType == 2">图片</span>
                 <span v-if="item.contentType == 3">卡片</span>
-                <span v-if="item.contentType == 4">小程序</span>
+                <span v-if="item.contentType == 4">小程序
+                编号:{{contentDialog.videoId}}
+                </span>
                 <span v-if="item.contentType == 5">文件</span>
                 <span v-if="item.contentType == 6">视频</span>
                 <span v-if="item.contentType == 7">语音</span>
@@ -383,6 +388,7 @@ export default {
   },
   data() {
     return {
+
       videoNumOptions: {
         title: '选择视频号',
         open: false,
@@ -435,6 +441,7 @@ export default {
       contentDialog:{
         isDialogVisible:false,
         json: [],
+        videoId:null,
       },
       // 弹出层标题
       title: "",
@@ -484,6 +491,15 @@ export default {
 
   },
   methods: {
+      // 获取发送用户类型文本
+      getOnlyUnregisteredText(value) {
+          const typeMap = {
+              1: '已注册',
+              2: '未注册',
+              3: '未对话'
+          };
+          return typeMap[value] || value;
+      },
 
     qwUserVideoResult(val) {
 
@@ -594,9 +610,8 @@ export default {
       // 替换非法换行符等控制字符
       const sanitizedJson = contentJson.replace(/[\u0000-\u001F\u007F]/g, '');
       const parsedData = JSON.parse(sanitizedJson);
-
       this.contentDialog.json = parsedData.setting;
-
+      this.contentDialog.videoId=parsedData.videoId
       this.contentDialog.isDialogVisible = true;
     },
     /** 搜索按钮操作 */

+ 15 - 4
src/views/qw/sopTemp/deptIndex.vue

@@ -440,6 +440,7 @@ export default {
   components: {Treeselect},
   data() {
     return {
+      projectFrom:process.env.VUE_APP_PROJECT_FROM,
       // 遮罩层
       loading: true,
       companysloading: false,
@@ -780,7 +781,12 @@ export default {
       // if (row.sendType==4) {
       //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/3`)
       // }else{
-      this.$router.push(`/qw/sopTemp/updateSopTemp/${row.id}/3`)
+        if (this.projectFrom ==='sxjz'){
+            this.$router.push(`/qw/sopTemp/updateSopTempSXJZ/${row.id}/3`)
+        }else {
+            this.$router.push(`/qw/sopTemp/updateSopTemp/${row.id}/3`)
+        }
+
       // }
     },
     /** 修改按钮操作 */
@@ -798,9 +804,14 @@ export default {
       // if (row.sendType==4) {
       //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/1`)
       // }else{
-      let url = `/qw/sopTemp/updateSopTemp/${row.id}/1`;
-      console.info(url)
-      this.$router.push(url)
+        if (this.projectFrom ==='sxjz'){
+            let url = `/qw/sopTemp/updateSopTempSXJZ/${row.id}/1`;
+            this.$router.push(url)
+        }else {
+            let url = `/qw/sopTemp/updateSopTemp/${row.id}/1`;
+            this.$router.push(url)
+        }
+
       // }
     },
     /** 修改按钮操作 */

+ 16 - 4
src/views/qw/sopTemp/index.vue

@@ -475,6 +475,8 @@ export default {
   components: {Treeselect},
   data() {
     return {
+       projectFrom:process.env.VUE_APP_PROJECT_FROM,
+
       officialForm:{
         tempId:null,
         isOfficial:null,
@@ -825,7 +827,12 @@ export default {
       // if (row.sendType==4) {
       //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/3`)
       // }else{
-      this.$router.push(`/qw/sopTemp/updateSopTemp/${row.id}/3`)
+        if (this.projectFrom ==='sxjz'){
+            this.$router.push(`/qw/sopTemp/updateSopTempSXJZ/${row.id}/3`)
+        }else {
+            this.$router.push(`/qw/sopTemp/updateSopTemp/${row.id}/3`)
+        }
+
       // }
     },
     /** 修改按钮操作 */
@@ -843,9 +850,14 @@ export default {
       // if (row.sendType==4) {
       //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/1`)
       // }else{
-      let url = `/qw/sopTemp/updateSopTemp/${row.id}/1`;
-      console.info(url)
-      this.$router.push(url)
+        if (this.projectFrom ==='sxjz'){
+            let url = `/qw/sopTemp/updateSopTempSXJZ/${row.id}/1`;
+            this.$router.push(url)
+        }else {
+            let url = `/qw/sopTemp/updateSopTemp/${row.id}/1`;
+            this.$router.push(url)
+        }
+
       // }
     },
     /**

+ 17 - 5
src/views/qw/sopTemp/myIndex.vue

@@ -422,6 +422,8 @@ export default {
   components: {Treeselect},
   data() {
     return {
+
+      projectFrom:process.env.VUE_APP_PROJECT_FROM,
       // 遮罩层
       loading: true,
       companysloading: false,
@@ -677,7 +679,12 @@ export default {
       // if (row.sendType==4) {
       //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/3`)
       // }else{
-      this.$router.push(`/qw/sopTemp/updateSopTemp/${row.id}/3`)
+        if (this.projectFrom ==='sxjz'){
+            this.$router.push(`/qw/sopTemp/updateSopTempSXJZ/${row.id}/3`)
+        }else {
+            this.$router.push(`/qw/sopTemp/updateSopTemp/${row.id}/3`)
+        }
+
       // }
     },
     /** 修改按钮操作 */
@@ -694,10 +701,15 @@ export default {
     handleUpdate2(row) {
       // if (row.sendType==4) {
       //   this.$router.push(`/qw/sopTemp/updateAiChatTemp/${row.id}/1`)
-      // }else{
-      let url = `/qw/sopTemp/updateSopTemp/${row.id}/1`;
-      console.info(url)
-      this.$router.push(url)
+
+        if (this.projectFrom ==='sxjz'){
+            let url = `/qw/sopTemp/updateSopTempSXJZ/${row.id}/1`;
+            this.$router.push(url)
+        }else {
+            let url = `/qw/sopTemp/updateSopTemp/${row.id}/1`;
+            this.$router.push(url)
+        }
+
       // }
     },
     /** 修改按钮操作 */

+ 3275 - 0
src/views/qw/sopTemp/updateSopTempSXJZ.vue

@@ -0,0 +1,3275 @@
+<template>
+  <div class="app-container sop-temp-container"
+       v-loading="!form.id"
+       element-loading-text="页面加载中..."
+       element-loading-background="rgba(255, 255, 255, 0.95)">
+    <!-- 页面头部 -->
+<!--    <div class="page-header">-->
+<!--      <div class="header-title">-->
+<!--        <i class="el-icon-document"></i>-->
+<!--        <span v-if="this.form.sendType == 1 && formType==1">sop规则【修改企微接口】模板</span>-->
+<!--        <span v-if="this.form.sendType == 1 && formType==2">sop规则【复制企微接口】模板</span>-->
+<!--        <span v-if="this.form.sendType == 1 && formType==3">sop规则【查看企微接口】模板</span>-->
+<!--        <span v-if="this.form.sendType == 2 && formType==1">sop规则【修改群发助手】模板</span>-->
+<!--        <span v-if="this.form.sendType == 2 && formType==2">sop规则【复制群发助手】模板</span>-->
+<!--        <span v-if="this.form.sendType == 2 && formType==3">sop规则【查看群发助手】模板</span>-->
+<!--      </div>-->
+<!--      <div class="header-subtitle">-->
+<!--        <span class="template-id">模板编号:{{ this.form.id }}</span>-->
+<!--      </div>-->
+<!--    </div>-->
+
+    <!-- 表单内容区 -->
+    <div class="form-content">
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px" class="sop-form">
+        <!-- 基本信息卡片 -->
+        <el-card class="info-card" shadow="never">
+          <div slot="header" class="card-header">
+            <i class="el-icon-info"></i>
+            <span>基本信息</span>
+          </div>
+          <div class="info-items">
+            <div class="info-item">
+              <span class="info-label">名称</span>
+              <span class="info-value">{{ form.name }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">状态</span>
+              <el-tag v-for="dict in statusOptions" v-if="dict.dictValue == form.status" :key="dict.dictValue" size="small">{{ dict.dictLabel }}</el-tag>
+            </div>
+            <div class="info-item">
+              <span class="info-label">间隔天数</span>
+              <span class="info-value">{{ form.gap }}天</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">模板编号</span>
+              <span class="info-value">{{ form.id }}</span>
+            </div>
+          </div>
+        </el-card>
+        <!-- 规则配置 -->
+        <el-form-item label="规则" prop="setting" class="rule-form-item">
+          <div class="rule-actions">
+            <el-button
+              type="primary"
+              icon="el-icon-plus"
+              size="small"
+              @click='addSetting()'
+              v-if="formType != 3 && roles.includes('add_sop_temp_day')">
+              添加天数
+            </el-button>
+            <el-button
+              type="primary"
+              icon="el-icon-sort"
+              size="small"
+              plain
+              @click='openUpdateDaySorts()'
+              v-if="formType != 3">
+              修改天数排序
+            </el-button>
+            <el-button
+              type="primary"
+              icon="el-icon-sort"
+              size="small"
+              plain
+              @click='openUpdateSorts()'
+              v-if=" formType != 3">
+              修改规则排序
+            </el-button>
+          </div>
+          <el-tabs v-model="tabIndex" type="card" @tab-remove="delSetting" v-if="setting && setting.length > 0"
+                   :before-leave="leave" @tab-click="tabClick" class="rule-tabs">
+            <el-tab-pane v-for="(item, index) in setting" :closable="formType != 3 && roles.includes('del_sop_temp_day')" :key="index" :name="index + ''">
+              <el-badge slot="label" :is-dot="!item.id" class="item" style="display: inline-block">
+                <span>{{ '第' + item.dayNum + '天' }}</span>
+              </el-badge>
+              <el-row v-loading="loading" element-loading-text="加载中..." element-loading-background="rgba(255, 255, 255, 0.9)" class="tab-content-row">
+                <el-col :span="22">
+                  <el-card shadow="never" class="day-content-card">
+                    <el-form :model="item" label-width="100px" class="content-form">
+                      <el-form-item v-if="form.sendType != 4" label="内容名称" style="height: 50px;">
+                        <el-input :disabled="formType == 3" v-model="item.name"
+                                  placeholder="内容名称,仅内部可见"/>
+                      </el-form-item>
+                      <el-form-item label="课程" v-if="form.sendType == 11 && item.content && item.content.length > 0" required>
+                        <el-select :disabled="((formType == 3 || form.sendType == 11) && item.id != null) || !roles.includes('update_sop_temp_scourse')" v-model="item.content[0].courseId"
+                                   placeholder="请选择课程" style=" margin-right: 10px;" size="mini" remote
+                                   filterable
+                                   @change="courseChangeUpdate(item.content[0], index, 0)">
+                          <el-option
+                            v-for="dict in courseList"
+                            :key="dict.dictValue"
+                            :label="dict.dictLabel"
+                            :value="parseInt(dict.dictValue)"
+                          />
+                        </el-select>
+                        <el-select :disabled="((formType == 3 || form.sendType == 11) && item.id != null) || !roles.includes('update_sop_temp_scourse')" v-model="item.content[0].videoId"
+                                   placeholder="请选择小节" size="mini" style=" margin-right: 10px;" remote
+                                   filterable
+                                   @change="videoIdChange(item.content[0],index,0)">
+                          <el-option
+                            v-for="dict in videoList[0]"
+                            :key="dict.dictValue"
+                            :label="dict.dictLabel"
+                            :value="parseInt(dict.dictValue)"
+                          />
+                        </el-select>
+                      </el-form-item>
+                      <el-form-item label="规则" class="rule-content-item">
+                        <div v-for="(content, contentIndex) in item.content" :key="contentIndex"
+                             class="content-item-card">
+                          <div class="content-item-header">
+                            <span class="content-item-title">
+                              <i class="el-icon-s-order"></i>
+                              规则 {{ contentIndex + 1 }}
+                            </span>
+                          </div>
+                          <el-row>
+                            <el-col el-col :span="22">
+                              <el-form :model="content" label-width="100px" class="content-form">
+                                <div v-if="item.dayNum==1">
+                                <el-form-item label="时间" v-if="form.sendType != 4" class="inline-form-item">
+                                  <el-time-picker
+                                    :disabled="formType == 3 || !roles.includes('update_sop_temp_time')"
+                                    class="custom-input"
+                                    v-model="content.time"
+                                    value-format="HH:mm"
+                                    format="HH:mm"
+                                    :picker-options="{ selectableRange: ['01:00:00 - 01:59:00','05:00:00 - 23:59:59'] }"
+                                    placeholder="时间"
+                                    style="width: 120px;">
+                                  </el-time-picker>
+                                </el-form-item>
+                                </div>
+                                <div v-else>
+                                  <el-form-item label="时间" v-if="form.sendType != 4" class="inline-form-item">
+                                    <el-time-picker
+                                      :disabled="formType == 3 || !roles.includes('update_sop_temp_time')"
+                                      class="custom-input"
+                                      v-model="content.time"
+                                      :picker-options="{ selectableRange: ['00:01:00 - 00:59:00','05:00:00 - 23:59:59']}"
+                                      value-format="HH:mm"
+                                      format="HH:mm"
+                                      placeholder="时间"
+                                      style="width: 120px;">
+                                    </el-time-picker>
+                                  </el-form-item>
+                                </div>
+                                <el-form-item label="官方群发" v-if="contentIndex==0 && content.type==2  && form.sendType != 4" class="switch-form-item">
+                                  <el-switch
+                                    v-model="content.isOfficial"
+                                    active-color="#13ce66"
+                                    inactive-color="#DCDFE6"
+                                    active-value="1"
+                                    inactive-value="0">
+                                  </el-switch>
+                                  <div class="form-tip-info">
+                                    <i class="el-icon-info"></i>
+                                    请注意:官方群发 【第1天】 只能设置1点-1点30 的时间,【其他天数时间】 只能设置0点-0点30。此处仅为生成发送记录时间,实际发送可由 销售在公司规定的 【暂早上8点(前!!)】,在企业微信的【群发助手】处点击发送
+                                  </div>
+                                </el-form-item>
+
+                                <el-form-item label="是否@所有人" v-if="form.sendType != 4" class="switch-form-item">
+                                  <el-switch
+                                    v-model="content.isAtAll"
+                                    active-color="#13ce66"
+                                    inactive-color="#DCDFE6"
+                                    :active-value="1"
+                                    :inactive-value="0">
+                                  </el-switch>
+                                  <div class="form-tip-info">
+                                    <i class="el-icon-info"></i>
+                                    开启后将在群发消息时@所有人(默认关闭)
+                                  </div>
+                                </el-form-item>
+
+                                <el-form-item label="消息类型" v-if="form.sendType != 4">
+                                  <el-select :disabled="formType == 3" v-model="content.courseType"
+                                             placeholder="请选择消息类型" size="mini"
+                                             style=" margin-right: 10px;" v-if="content.type != 4 ">
+                                    <el-option
+                                      v-for="dict in sysFsSopWatchStatus"
+                                      :key="dict.dictValue"
+                                      :label="dict.dictLabel"
+                                      :value="Number(dict.dictValue)"
+                                    />
+                                  </el-select>
+                                </el-form-item>
+                                <el-form-item label="消息类别" v-if="form.sendType != 4 && form.sendType != 5" class="msg-category-item">
+                                  <el-radio-group v-model="content.type"
+                                                  :disabled="formType == 3 || content.isOfficial === '1'"
+                                                  @change="updateHtml(() => content.contentType = '1',content)">
+                                    <el-radio :label="1">普通</el-radio>
+                                    <el-radio :label="2">课程</el-radio>
+                                    <el-radio :label="4">AI触达</el-radio>
+                                    <el-radio :label="5">打标签</el-radio>
+                                    <el-radio :label="20">直播间</el-radio>
+                                  </el-radio-group>
+                                </el-form-item>
+                                <el-form-item label="直播间" v-if="content.type == 20">
+                                      <el-select  v-model="content.liveId"
+                                                  filterable
+                                                  placeholder="请选择直播间" size="mini"
+                                                  @change="liveChangeContent(content)" >
+                                        <el-option
+                                          v-for="dict in liveList"
+                                          :key="dict.liveId"
+                                          :label="dict.liveName"
+                                          :value="dict.liveId"
+                                        />
+                                      </el-select>
+                                </el-form-item>
+                                <el-form-item label="课程选择" v-if="content.type == 2 && form.sendType != 5" required class="course-select-item">
+                                  <div class="course-select-row">
+                                    <el-select :disabled="formType == 3 || form.sendType == 11 || !roles.includes('edit_sop_temp_content')" v-model="content.courseId"
+                                               placeholder="请选择课程" size="small" remote
+                                               filterable
+                                               @change="courseChangeUpdate(content,index,contentIndex)">
+                                      <el-option
+                                        v-for="dict in courseList"
+                                        :key="dict.dictValue"
+                                        :label="dict.dictLabel"
+                                        :value="parseInt(dict.dictValue)"
+                                      />
+                                    </el-select>
+                                    <el-select :disabled="formType == 3 || form.sendType == 11 || !roles.includes('edit_sop_temp_content')" v-model="content.videoId"
+                                               placeholder="请选择小节" size="small" remote
+                                               filterable
+                                               @change="videoIdChange(content,index,contentIndex)">
+                                      <el-option
+                                        v-for="dict in videoList[contentIndex]"
+                                        :key="dict.dictValue"
+                                        :label="dict.dictLabel"
+                                        :value="parseInt(dict.dictValue)"
+                                      />
+                                    </el-select>
+                                  </div>
+                                  <div class="course-type-row" v-if="content.type != 4">
+                                    <el-select :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')" v-model="content.courseType"
+                                               placeholder="请选择消息类型" size="small">
+                                      <el-option
+                                        v-for="dict in sysFsSopWatchStatus"
+                                        :key="dict.dictValue"
+                                        :label="dict.dictLabel"
+                                        :value="Number(dict.dictValue)"
+                                      />
+                                    </el-select>
+                                  </div>
+
+                                </el-form-item>
+                                <el-form-item label="AI触达" v-if="content.type == 4">
+                                  <el-select :disabled="formType == 3" v-model="content.aiTouch"
+                                             placeholder="请选择Ai触达类型" size="mini"
+                                             style=" margin-right: 10px;" v-if="content.type == 4 ">
+                                    <el-option label="非首次交流" value="非首次交流"></el-option>
+                                    <el-option label="首次交流1" value="首次交流1"></el-option>
+                                    <el-option label="首次交流2" value="首次交流2"></el-option>
+                                    <el-option label="交流状态1" value="交流状态1"></el-option>
+                                    <el-option label="交流状态2" value="交流状态2"></el-option>
+                                    <el-option label="交流状态3" value="交流状态3"></el-option>
+                                  </el-select>
+                                </el-form-item>
+                                <el-form-item label="添加标签" prop="addTag" v-if="content.type == 5 "
+                                              v-model="content.addTag">
+
+                                  <el-tag
+                                    :key="tag"
+                                    v-for="tag in content.addTag"
+                                    closable
+                                    :disable-transitions="false"
+                                    @close="handleClose(contentIndex,tag,content)">
+                                    {{ tag }}
+                                  </el-tag>
+                                  <el-input
+                                    style="width:110px"
+                                    class="input-new-tag"
+                                    v-if="addTag[contentIndex] && addTag[contentIndex].inputVisible"
+                                    v-model="addTag[contentIndex].inputValue"
+                                    ref="saveTagInput"
+                                    size="small"
+                                    @keyup.enter.native="handleInputConfirm(contentIndex,content)"
+                                    @blur="handleInputConfirm(contentIndex,content)"
+                                  >
+                                  </el-input>
+                                  <el-button v-else class="button-new-tag" size="small" style="width: 110px"
+                                             @click="showInput(contentIndex)">新增标签
+                                  </el-button>
+                                </el-form-item>
+
+                                <el-form-item label="移除标签" prop="remarkMobiles" v-if="content.type == 5 ">
+
+                                  <el-tag
+                                    :key="tag"
+                                    v-for="tag in content.delTag"
+                                    closable
+                                    :disable-transitions="false"
+                                    @close="handleCloseDel(contentIndex,tag,content)">
+                                    {{ tag }}
+                                  </el-tag>
+                                  <el-input
+                                    style="width:110px"
+                                    class="input-new-tag"
+                                    v-if="addTag[contentIndex] && addTag[contentIndex].delTagVisible"
+                                    v-model="addTag[contentIndex].delTagValue"
+                                    ref="saveTagInputDel"
+                                    size="small"
+                                    @keyup.enter.native="handleInputConfirmDel(contentIndex,content)"
+                                    @blur="handleInputConfirmDel(contentIndex,content)"
+                                  >
+                                  </el-input>
+                                  <el-button v-else class="button-new-tag" size="small" style="width: 110px"
+                                             @click="showInputDel(contentIndex)">新增标签
+                                  </el-button>
+
+                                </el-form-item>
+
+                                <div v-for="(setList, setIndex) in content.setting" :key="setIndex"
+                                     class="setting-item-card"
+                                     v-if="content.type != 4 && content.type != 5">
+                                  <div class="setting-item-header">
+                                    <span class="setting-item-title">
+                                      <i class="el-icon-document"></i>
+                                      内容 {{ setIndex + 1 }}
+                                    </span>
+                                  </div>
+                                  <el-row class="setting-content">
+                                    <el-col :span="22">
+                                      <el-form  :model="setList" label-width="100px" class="content-form">
+                                        <el-form-item label="内容类别" class="content-type-item">
+                                          <div v-if="form.sendType == 1 ">
+                                            <el-radio-group v-model="setList.contentType" :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')">
+                                              <!--                                              <el-radio  :label="item.dictValue" v-for="item in sysQwSopContentType">{{item.dictLabel}}</el-radio>-->
+                                              <el-radio
+                                                v-for="item in sysQwSopContentType"
+                                                :key="item.dictValue"
+                                                :label="item.dictValue"
+                                                :disabled="item.dictValue === '1' && content.setting.some(s => s.contentType == '1') ">
+                                                {{ item.dictLabel }}
+                                              </el-radio>
+                                            </el-radio-group>
+                                          </div>
+                                          <div v-if="form.sendType == 2">
+                                            <el-radio-group v-model="setList.contentType"
+                                                            :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')"
+                                                            @change="handleContentTypeChange(content,index,contentIndex,setIndex, item, 'contentType', $event)">
+                                              <el-radio
+                                                :key="item.dictValue"
+                                                :label="item.dictValue"
+                                                :disabled="(content.type!=2 && item.dictValue === '9') || (content.isOfficial==1 && ['5','6','7','8','9'].includes(item.dictValue))"
+                                                v-for="item in sysQwSopAiContentType">{{ item.dictLabel }}
+                                              </el-radio>
+                                            </el-radio-group>
+                                          </div>
+                                          <div v-if=" form.sendType == 4">
+                                            <el-radio-group v-model="setList.contentType"
+                                                            :disabled="formType == 3"
+                                                            @change="handleContentTypeChange(content,index,contentIndex,setIndex)">
+                                              <el-radio
+                                                :key="item.dictValue"
+                                                :label="item.dictValue"
+                                                :disabled="((content.type!=2 || form.sendType == 4) && (item.dictValue === '8' || item.dictValue === '9') || (content.isOfficial==1 && ['5','6','7','8','9'].includes(item.dictValue)))"
+                                                v-for="item in sysQwSopAiContentType">{{ item.dictLabel }}
+                                              </el-radio>
+                                            </el-radio-group>
+                                          </div>
+                                          <div v-if="form.sendType == 11">
+                                            <el-radio-group v-model="setList.contentType"
+                                                            :disabled="formType == 3 || (form.sendType == 11 && contentIndex != 0 && setIndex == 0)"
+                                                            @change="handleContentTypeChange(content,index,contentIndex,setIndex, item, 'contentType', $event)">
+                                              <el-radio
+                                                :key="item.dictValue"
+                                                :label="item.dictValue"
+                                                :disabled="(content.type!=2 && item.dictValue === '9') || (content.isOfficial==1 && ['5','6','7','8','9'].includes(item.dictValue))"
+                                                v-for="item in sysQwSopAiContentType"
+                                                v-if="courseTypeList.includes(item.dictValue)">{{ item.dictLabel }}
+                                              </el-radio>
+                                            </el-radio-group>
+                                          </div>
+                                        </el-form-item>
+                                        <el-form-item label="内容">
+                                          <el-input :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')" v-if="setList.contentType == 1 || setList.contentType == 15"
+                                                    v-model="setList.value"
+                                                    type="textarea" :rows="3" placeholder="内容"
+                                                    style="width: 90%;margin-top: 10px;"
+                                                    @keydown.native="handleKeydown($event, index, contentIndex, setIndex)"
+                                                    :ref="`textarea-${index}-${contentIndex}-${setIndex}`"
+                                          />
+
+                                          <div class="content-actions">
+                                            <el-button
+                                              v-if="(setList.contentType == 1 || setList.contentType == 15) && roles.includes('edit_sop_temp_content')"
+                                              type="text"
+                                              icon="el-icon-plus"
+                                              size="small"
+                                              @click="toggleSalesCall(index, contentIndex, setIndex)">
+                                              {{ setList.isSalesCallAdded ? '移除#销售称呼#' : '添加#销售称呼#' }}
+                                            </el-button>
+                                            <el-button
+                                              v-if="(setList.contentType == 1 || setList.contentType == 15) && roles.includes('edit_sop_temp_content')"
+                                              type="text"
+                                              icon="el-icon-user"
+                                              size="small"
+                                              @click="toggleUserNameCall(index, contentIndex, setIndex)">
+                                              {{ setList.isUserNameCallAdded ? '移除#客户称呼#' : '添加#客户称呼#' }}
+                                            </el-button>
+                                          </div>
+
+                                          <div v-if="setList.contentType == 2">
+                                            <el-card class="box-card image-card">
+                                              <div slot="header" class="card-header-mini">
+                                                <i class="el-icon-picture-outline"></i>
+                                                <span>图片内容</span>
+                                              </div>
+                                              <el-form-item label="上传图片" required>
+                                                <ImageUpload :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')"
+                                                           v-model="setList.imgUrl"
+                                                           type="image" :num="1" :width="150" :height="150"/>
+                                              </el-form-item>
+                                            </el-card>
+                                          </div>
+
+                                          <div
+                                            v-if="(setList.contentType == 3 || item.contentType ==9) || (setList.contentType == 9 && content.type==2 )">
+                                            <el-card class="box-card link-card">
+                                              <div slot="header" class="card-header-mini">
+                                                <i class="el-icon-link"></i>
+                                                <span>链接内容</span>
+                                              </div>
+                                              <el-form-item label="链接标题" label-width="100px" required>
+                                                <el-input :disabled="formType == 3 || (form.sendType == 11 && contentIndex != 0 && setIndex == 0)" v-model="setList.linkTitle"
+                                                          @change="updateAll(setIndex, item, 'linkTitle', $event)"
+                                                          placeholder="请输入链接标题"
+                                                          style="width: 90%;"/>
+                                              </el-form-item>
+                                              <el-form-item label="链接描述" label-width="100px" required>
+                                                <el-input :disabled="formType == 3 || (form.sendType == 11 && contentIndex != 0 && setIndex == 0)" type="textarea" :rows="3"
+                                                          v-model="setList.linkDescribe"
+                                                          @change="updateAll(setIndex, item, 'linkDescribe', $event)"
+                                                          placeholder="请输入链接描述"
+                                                          style="width: 90%;margin-top: 1%;"/>
+                                              </el-form-item>
+                                              <el-form-item label="链接封面" label-width="100px" required>
+                                                <ImageUpload :disabled="formType == 3 || (form.sendType == 11 && contentIndex != 0 && setIndex == 0)" v-model="setList.linkImageUrl"
+                                                             type="image" :num="1"
+                                                             @input="updateAll(setIndex, item, 'linkImageUrl', $event)"
+                                                             :file-size="2" :width="150" :height="150"
+                                                             style="margin-top: 1%;"/>
+                                              </el-form-item>
+                                              <div v-if="content.type != 2" class="link-url-section">
+                                                <el-form-item label="链接地址" label-width="100px" required>
+                                                  <el-input :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')" v-model="setList.linkUrl"
+                                                            placeholder="请输入链接地址"
+                                                            style="width: 90%;"/>
+                                                </el-form-item>
+                                              </div>
+                                              <div v-if="content.type == 2" class="link-tip-section">
+                                                <el-form-item label="链接地址" label-width="100px">
+                                                  <el-tag type="warning" v-model="setList.isBindUrl = 1 ">
+                                                    <i class="el-icon-info"></i>
+                                                    选择的课程小节即为卡片链接地址
+                                                  </el-tag>
+                                                </el-form-item>
+                                              </div>
+                                            </el-card>
+                                          </div>
+
+                                          <div v-if="setList.contentType == 4 || setList.contentType == 10 || setList.contentType == 17  || setList.contentType == 23">
+                                            <el-card class="box-card miniprogram-card">
+                                              <div slot="header" class="card-header-mini">
+                                                <i class="el-icon-mobile-phone"></i>
+                                                <span>小程序内容</span>
+                                              </div>
+                                              <el-form-item label="标题" prop="miniprogramTitle" required>
+                                                <el-input v-model="setList.miniprogramTitle"
+                                                          :disabled="formType == 3 || (form.sendType == 11 && contentIndex != 0 && setIndex == 0)"
+                                                          @change="updateAll(setIndex, item, 'miniprogramTitle', $event)"
+                                                          placeholder="请输入小程序消息标题,最长为64字节" :rows="2"
+                                                          maxlength="64" type="textarea"
+                                                          @input="checkByteLength(content,setList.contentType,content.isOfficial)"/>
+                                              </el-form-item>
+                                              <el-form-item label="封面" prop="miniprogramPicUrl" required>
+                                                <ImageUpload v-if="content.isOfficial !== '1'"
+                                                             @change="updateAll(setIndex, item, 'miniprogramPicUrl', $event)"
+                                                             :disabled="formType == 3 || (form.sendType == 11 && contentIndex != 0 && setIndex == 0)"
+                                                             v-model="setList.miniprogramPicUrl" type="image" :num="10"
+                                                             :width="150" :height="150"/>
+                                              </el-form-item>
+                                              <el-form-item label="appid" prop="miniprogramAppid" v-show="false">
+                                                <el-input v-model="setList.miniprogramAppid='wx73f85f8d62769119' " :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')"
+                                                          disabled/>
+                                              </el-form-item>
+                                              <el-form-item label="page路径" prop="miniprogramPage" v-show="setList.contentType == 10"
+                                                            label-width="100px" style="margin-left: -30px">
+                                                <el-input v-model="setList.miniprogramPage"
+                                                          :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')"
+                                                          placeholder="小程序消息打开后的路径" type="textarea" :rows="3" />
+                                              </el-form-item>
+                                            </el-card>
+                                          </div>
+                                          <div v-if="setList.contentType == 12 || setList.contentType == 18 || setList.contentType == 19 || setList.contentType == 24">
+                                            <el-card class="box-card miniprogram-card">
+                                              <div slot="header" class="card-header-mini">
+                                                <i class="el-icon-video-camera"></i>
+                                                <span>直播小程序</span>
+                                              </div>
+                                              <el-form-item label="直播间" required>
+                                                <el-select  v-model="setList.liveId"
+                                                            placeholder="请选择直播间" size="mini"
+                                                            filterable
+                                                            :disabled="content.type==20"
+                                                            @change="liveChange(setList)" >
+                                                  <el-option
+                                                    v-for="dict in liveList"
+                                                    :key="dict.liveId"
+                                                    :label="dict.liveName"
+                                                    :value="dict.liveId"
+                                                  />
+                                                </el-select>
+                                              </el-form-item>
+
+                                              <el-form-item label="标题" prop="miniprogramTitle" required>
+                                                <el-input  :disabled="content.type==20" v-model="setList.miniprogramTitle" placeholder="请输入小程序消息标题,最长为64字节" :rows="2" maxlength="64"
+                                                          type="textarea" @input="checkByteLength(content,setList.contentType,content.isOfficial)"   />
+                                              </el-form-item>
+                                              <el-form-item label="封面" prop="miniprogramPicUrl" required>
+                                                <ImageUpload  :disabled="content.type==20" v-model="setList.miniprogramPicUrl" type="image" :num="10" :width="150" :height="150" />
+                                              </el-form-item>
+                                              <el-form-item label="appid" prop="miniprogramAppid" v-show="false">
+                                                <el-input v-model="setList.miniprogramAppid='wxcfd4cd6e2375e42f' " disabled />
+                                              </el-form-item>
+                                              <el-form-item label="page路径" prop="miniprogramPage"  v-show="false" label-width="100px" style="margin-left: -30px">
+                                                <el-input v-model="setList.miniprogramPage" placeholder="小程序消息打开后的路径"  disabled />
+                                              </el-form-item>
+                                            </el-card>
+                                          </div>
+
+
+                                          <div v-if="setList.contentType == 5">
+                                            <el-card class="box-card upload-card">
+                                              <div slot="header" class="card-header-mini">
+                                                <i class="el-icon-document"></i>
+                                                <span>文件上传</span>
+                                              </div>
+                                              <el-form-item label="上传文件" prop="fileUrl" label-width="100px" required>
+                                              <el-upload
+                                                :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')"
+                                                v-model="setList.fileUrl"
+                                                class="avatar-uploader"
+                                                :action="uploadUrl"
+                                                :show-file-list="false"
+                                                :on-success="(res, file) => handleAvatarSuccessFile(res, file, setList)"
+                                                :before-upload="beforeAvatarUploadFile">
+                                                <i class="el-icon-plus avatar-uploader-icon"></i>
+                                              </el-upload>
+                                              <el-link v-if="setList.fileUrl" type="primary"
+                                                       :href="downloadUrl(setList.fileUrl)" download>
+                                                {{ setList.fileUrl }}
+                                              </el-link>
+                                            </el-form-item>
+                                            </el-card>
+                                          </div>
+
+                                          <div v-if="setList.contentType == 6">
+                                            <el-card class="box-card upload-card">
+                                              <div slot="header" class="card-header-mini">
+                                                <i class="el-icon-video-play"></i>
+                                                <span>视频上传</span>
+                                              </div>
+                                              <el-form-item label="上传视频" prop="videoUrl" label-width="100px" required>
+                                              <el-upload
+                                                :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')"
+                                                v-model="setList.videoUrl"
+                                                class="avatar-uploader"
+                                                :action="uploadUrl"
+                                                :show-file-list="false"
+                                                :on-success="(res, file) => handleAvatarSuccessVideo(res, file, setList)"
+                                                :before-upload="beforeAvatarUploadVideo">
+                                                <i class="el-icon-plus avatar-uploader-icon"></i>
+                                              </el-upload>
+                                              <video v-if="setList.videoUrl"
+                                                     :src="setList.videoUrl"
+                                                     controls style="width: 200px;height: 100px">
+                                              </video>
+                                            </el-form-item>
+                                            </el-card>
+                                          </div>
+                                          <div v-if="setList.contentType == 7 || setList.contentType == 16">
+                                            <el-input
+                                              :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')"
+                                              v-model="setList.value"
+                                              type="textarea" :rows="3" maxlength="66" show-word-limit
+                                              placeholder="输入要转为语音的内容"
+                                              style="width: 90%;margin-top: 10px;"
+                                              @input="handleInputVideoText(setList.value,setList)"/>
+
+
+                                          </div>
+                                          <div v-if="setList.contentType == 8 ">
+                                            <el-button :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')" type="primary"
+                                                       style="margin-bottom: 1%"
+                                                       @click="hanldeSelectVideoNum(content,index,contentIndex,setIndex)">
+                                              选择视频号
+                                            </el-button>
+                                            <el-card class="box-card" v-if="setList.coverUrl">
+                                              <el-form-item label="封面标题:" label-width="100px">
+                                                <el-input :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')" v-model="setList.nickname"
+                                                          style="width: 90%;margin-bottom: 1%" disabled/>
+                                              </el-form-item>
+                                              <el-form-item label="头像:" label-width="100px">
+                                                <el-image
+                                                  v-if="setList.avatar != null"
+                                                  :src="setList.avatar"
+                                                  :preview-src-list="[setList.avatar]"
+                                                  :style="{ width: '50px', height: '50px' }"
+                                                ></el-image>
+                                              </el-form-item>
+                                              <el-form-item label="封面:" label-width="100px">
+                                                <el-image
+                                                  v-if="setList.coverUrl != null"
+                                                  :src="setList.coverUrl"
+                                                  :preview-src-list="[setList.coverUrl]"
+                                                  :style="{ width: '200px', height: '200px' }"
+                                                ></el-image>
+
+                                              </el-form-item>
+                                              <el-form-item label="简介:" label-width="100px">
+                                                <el-input :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')" type="textarea" :rows="3"
+                                                          v-model="setList.desc"
+                                                          style="width: 90%;margin-top: 1%;" disabled/>
+                                              </el-form-item>
+                                              <el-form-item label="视频地址:" label-width="100px"
+                                                            style="margin-top: 1%">
+                                                <el-input :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')" v-model="setList.url"
+                                                          style="width: 90%;" disabled/>
+                                              </el-form-item>
+
+                                            </el-card>
+                                          </div>
+                                          <div v-if="setList.contentType == 14">
+                                            <el-card class="box-card luckybag-card">
+                                              <div slot="header" class="card-header-mini">
+                                                <i class="el-icon-present"></i>
+                                                <span>福袋设置</span>
+                                              </div>
+                                              <el-form-item label="福袋名称" required>
+                                                <el-select :disabled="formType == 3"    v-model="setList.luckyBagId"
+                                                           placeholder="请选择福袋" size="mini"
+                                                           @change="getLuckyBagStatus(setList)" >
+                                                  <el-option
+                                                    v-for="dict in luckyBagList"
+                                                    :key="dict.id"
+                                                    :label="dict.name"
+                                                    :value="dict.id"
+                                                  />
+                                                </el-select>
+                                              </el-form-item>
+                                              <el-form-item label="福袋状态">
+                                                <el-tag  :disabled="formType == 3" :type="setList.luckyBagDataStatus == 1 ? 'success' : 'danger'">
+                                                  {{ setList.luckyBagDataStatus == 1 ? '启用' : '禁用' }}
+                                                </el-tag>
+                                              </el-form-item>
+                                            </el-card>
+                                          </div>
+                                          <div v-if="setList.contentType == 11">
+                                            <el-input
+                                              :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')"
+                                              v-model="setList.value"
+                                              type="textarea"
+                                              :rows="5"
+                                              placeholder="请输入群公告内容"
+                                              style="width: 90%;margin-top: 10px;"
+                                            />
+                                          </div>
+
+                                          <div v-if="setList.contentType == 21">
+                                            <el-card class="box-card">
+                                              <el-form-item label="短信模板" >
+                                                <el-select v-model="setList.smsTemplateId"
+                                                           placeholder="请选择短信模板" size="mini"
+                                                           @change="getSmsTemplateInfo(setList)" >
+                                                  <el-option
+                                                    v-for="template in smsTemplateList"
+                                                    :key="template.tempId"
+                                                    :label="template.title"
+                                                    :value="template.tempId"
+                                                  />
+                                                </el-select>
+                                              </el-form-item>
+                                              <el-form-item label="内容预览" v-if="setList.smsTemplateContent">
+                                                <div style="color: #666; font-size: 12px; padding: 8px; border: 1px solid #eee; border-radius: 4px; background-color: #f9f9f9; max-height: 100px; overflow-y: auto;">
+                                                  {{ setList.smsTemplateContent }}
+                                                </div>
+                                              </el-form-item>
+                                            </el-card>
+                                          </div>
+                                        </el-form-item>
+
+                                        <el-form-item label="课节过期时间"
+                                                      v-if="content.type == 2 && (setList.isBindUrl == '1' || setList.contentType==4) && setList.contentType != 2  && setList.contentType != 5  && setList.contentType != 6 && setList.contentType != 8 && setList.contentType != 9 && setList.contentType != 10  ">
+                                          <el-row>
+                                            <el-input type="number" v-model="setList.expiresDays"
+                                                      :disabled="!roles.includes('edit_sop_temp_content') && (formType == 3 || (form.sendType == 11 && contentIndex != 0 && setIndex == 0))"
+                                                      @change="updateAll(setIndex, item, 'expiresDays', $event)"
+                                                      style="width: 200px">
+                                              <template slot="append">天</template>
+                                            </el-input>
+                                          </el-row>
+                                          <el-row>
+                                            <span class="tip">填写0或不填时,默认为系统配置的默认时间</span>
+                                          </el-row>
+                                        </el-form-item>
+
+                                      </el-form>
+                                      <el-form v-if="form.sendType == 4" :model="setList" label-width="100px" class="content-form">
+                                        <el-form-item label="添加客服" v-if="item.dayNum==1" prop="intervalTime" style="margin: 2%">
+                                          <el-input-number
+                                            v-model="setList.intervalTime"
+                                            :min="1"
+                                            :max="1440"
+                                            style="width:150px;margin-top: 10px;"
+                                          >
+                                          </el-input-number>
+                                          <span class="tip">单位:分钟,最大1440分钟(24小时)</span>
+                                        </el-form-item>
+                                          <el-form-item label="时间" v-if="item.dayNum > 1" class="inline-form-item">
+                                              <el-time-picker
+                                                  class="custom-input"
+                                                  v-model="setList.time"
+                                                  value-format="HH:mm"
+                                                  format="HH:mm"
+                                                  :picker-options="{ selectableRange: ['01:00:00 - 01:59:00','05:00:00 - 23:59:59'] }"
+                                                  placeholder="时间"
+                                                  style="width: 120px;">
+                                              </el-time-picker>
+                                          </el-form-item>
+                                          <el-form-item label="发送用户类型" style="margin: 2%">
+                                              <el-select v-model="setList.onlyUnregistered" placeholder="请选择" style="width: 150px;">
+                                                  <el-option label="已注册" value="1"></el-option>
+                                                  <el-option label="未注册" value="2"></el-option>
+                                                  <el-option label="未对话" value="3"></el-option>
+                                              </el-select>
+                                              <div class="form-tip-info">
+                                                  <i class="el-icon-info"></i>
+                                                   给用户发送的类型,默认为未对话(即客户没给你对话 就)
+                                              </div>
+                                          </el-form-item>
+                                      </el-form>
+                                    </el-col>
+                                    <el-col :span="1" :offset="1">
+                                      <i class="el-icon-delete" @click="delSetList(index,contentIndex,setIndex)"
+                                         style="margin-top: 20px;"
+                                         v-if="content.setting.length>1 && (formType != 3) && roles.includes('del_sop_temp_content') && !(form.sendType == 11 && setIndex == 0)"></i>
+                                    </el-col>
+                                  </el-row>
+                                </div>
+                                <el-link type="primary" class="el-icon-plus" :underline="false"
+                                         @click='addSetList(contentIndex,item.content)'
+                                         v-if="content.type != 4 && formType != 3 && roles.includes('add_sop_temp_content')">添加内容
+                                </el-link>
+                              </el-form>
+                            </el-col>
+                            <el-col :span="1" :offset="1">
+                              <i class="el-icon-delete" @click="delContent(index,contentIndex)"
+                                 style="margin-top: 20px;"
+                                 v-if="item.content.length>1 && formType != 3 && roles.includes('del_sop_temp_rule')"></i>
+                            </el-col>
+                          </el-row>
+                        </div>
+                        <el-link type="primary" class="el-icon-plus" :underline="false" @click='addContent(index)'
+                                 v-if="formType != 3 && form.sendType != 4 && roles.includes('add_sop_temp_rule')">添加规则
+                        </el-link>
+                      </el-form-item>
+                    </el-form>
+                  </el-card>
+                  <div style="float: right;" v-if="formType != 3 && roles.includes('update_sop_temp_day')">
+                    <el-button type="primary" @click="save" v-if="!item.voice || item.voice == 0">
+                      保存({{ '第' + (1 + (form.gap * index)) + '天' }})
+                    </el-button>
+                    <el-button type="primary" disabled v-if="item.voice == 1">语言生成中</el-button>
+                    <el-button type="primary" @click="leave(tabIndex)" v-if="item.voice == 1">刷新状态</el-button>
+                  </div>
+                </el-col>
+              </el-row>
+            </el-tab-pane>
+          </el-tabs>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <el-dialog :title="videoNumOptions.title" :visible.sync="videoNumOptions.open" style="width: 1500px;height: 100%"
+               append-to-body>
+      <userVideo ref="QwUserVideo" @videoResult="qwUserVideoResult"></userVideo>
+    </el-dialog>
+    <el-dialog title="修改天数排序" :visible.sync="openSort" width="600px" append-to-body class="sort-dialog">
+      <div class="sort-dialog-content">
+        <div class="sort-tip">
+          <i class="el-icon-info"></i>
+          拖动按钮调整顺序,红色表示顺序已变更
+        </div>
+        <draggable v-model="dayList" @end="onDragEndDay" class="sort-button-group">
+          <el-button
+            v-for="(item, index) in dayList"
+            :key="index"
+            :class="['sort-button', item.newDay != item.dayNum ? 'changed' : '']"
+            icon="el-icon-rank">
+            第{{ item.newDay }}天
+            <span v-if="item.newDay != item.dayNum" class="origin-day">(原第{{ item.dayNum }}天)</span>
+          </el-button>
+        </draggable>
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="openSort = false">取消</el-button>
+        <el-button type="primary" @click="saveSorts" icon="el-icon-check">保存</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog title="修改规则排序" :visible.sync="openSort2" width="600px" append-to-body class="sort-dialog">
+      <div class="sort-dialog-content">
+        <div class="sort-tip">
+          <i class="el-icon-info"></i>
+          拖动按钮调整规则发送顺序
+        </div>
+        <draggable v-model="ruleList" @end="onDragEnd" class="sort-button-group">
+          <el-button
+            v-for="(item, index) in ruleList"
+            :key="index"
+            class="sort-button"
+            icon="el-icon-time">
+            {{ item.time }}
+          </el-button>
+        </draggable>
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="openSort2 = false">取消</el-button>
+        <el-button @click="autoSortsRules" icon="el-icon-sort">自动排序</el-button>
+        <el-button type="primary" @click="saveSortsRules" icon="el-icon-check">保存</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+
+<style scoped>
+.el-button {
+  margin-left: 10px;
+}
+
+.red:hover {
+  color: #dbdbdb !important;
+}
+
+.red {
+  background-color: #F56C6C !important;
+  color: #fff !important;
+}
+
+.custom-input ::v-deep .el-input__inner {
+  height: 20px;
+  text-align: center;
+}
+
+.custom-input ::v-deep .el-input__icon {
+  line-height: 10px;
+}
+
+.el-icon-delete {
+  cursor: pointer;
+}
+
+::v-deep .el-badge__content.is-fixed {
+  top: 10px !important;
+  right: 0 !important;
+  width: 8px;
+}
+
+.tip {
+  color: #909399;
+  font-size: 12px;
+  margin-left: 10px;
+}
+
+.sortable-ghost {
+  background: rgb(26, 164, 255) !important;
+  color: #FFF !important;
+}
+
+/* ===== 页面头部样式 ===== */
+.sop-temp-container {
+  padding: 20px;
+  background: #f5f7fa;
+}
+
+.page-header {
+  background: linear-gradient(135deg, #1890ff 0%, #0066cc 100%);
+  padding: 20px 30px;
+  border-radius: 8px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 12px rgba(24, 144, 255, 0.15);
+}
+
+.header-title {
+  display: flex;
+  align-items: center;
+  color: #fff;
+  font-size: 18px;
+  font-weight: 600;
+  margin-bottom: 8px;
+}
+
+.header-title i {
+  font-size: 20px;
+  margin-right: 10px;
+}
+
+.header-subtitle {
+  padding-left: 30px;
+}
+
+.template-id {
+  display: inline-block;
+  background: rgba(255, 255, 255, 0.2);
+  padding: 4px 12px;
+  border-radius: 4px;
+  color: #fff;
+  font-size: 13px;
+  font-family: 'Courier New', monospace;
+  backdrop-filter: blur(10px);
+}
+
+/* ===== 表单内容区样式 ===== */
+.form-content {
+  background: #fff;
+  padding: 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+/* 基本信息卡片 */
+.info-card {
+  margin-bottom: 20px;
+  border: 1px solid #e8e8e8;
+  border-radius: 8px;
+}
+
+.info-card ::v-deep .el-card__header {
+  padding: 12px 20px;
+  background: #fafafa;
+  border-bottom: 1px solid #e8e8e8;
+}
+
+.info-card .card-header {
+  display: flex;
+  align-items: center;
+  font-weight: 600;
+  color: #333;
+}
+
+.info-card .card-header i {
+  margin-right: 8px;
+  font-size: 16px;
+  color: #1890ff;
+}
+
+/* 基本信息项目布局 */
+.info-items {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px;
+  padding: 8px 0;
+}
+
+.info-item {
+  display: inline-flex;
+  align-items: center;
+  background: #f8f9fa;
+  border: 1px solid #ebeef5;
+  border-radius: 6px;
+  padding: 8px 16px;
+  gap: 8px;
+}
+
+.info-label {
+  color: #606266;
+  font-size: 13px;
+  white-space: nowrap;
+}
+
+.info-label::after {
+  content: ':';
+}
+
+.info-value {
+  color: #303133;
+  font-size: 14px;
+  font-weight: 600;
+}
+
+.info-item .el-tag {
+  margin-left: 0;
+}
+
+/* 规则配置样式 */
+.rule-form-item {
+  margin-top: 20px;
+}
+
+.rule-form-item ::v-deep .el-form-item__label {
+  font-weight: 600;
+  color: #333;
+}
+
+.rule-actions {
+  margin-bottom: 16px;
+  padding: 12px;
+  background: #f9f9f9;
+  border-radius: 6px;
+  display: flex;
+  gap: 10px;
+  flex-wrap: wrap;
+}
+
+.rule-actions .el-button {
+  margin-left: 0;
+}
+
+/* 规则标签页样式 */
+.rule-tabs {
+  margin-top: 16px;
+}
+
+.rule-tabs ::v-deep .el-tabs__header {
+  margin-bottom: 20px;
+}
+
+.rule-tabs ::v-deep .el-tabs__item {
+  height: 42px;
+  line-height: 42px;
+  font-size: 14px;
+}
+
+.rule-tabs ::v-deep .el-tabs__item.is-active {
+  background: #1890ff;
+  color: #fff;
+  border-color: #1890ff;
+}
+
+/* 红点样式优化 - 未选中状态 */
+.rule-tabs ::v-deep .el-badge__content.is-dot {
+  width: 6px;
+  height: 6px;
+  top: -3px;
+  right: -8px;
+  background-color: #f56c6c;
+  border: none;
+  box-shadow: none;
+}
+
+/* 红点样式优化 - 选中状态 */
+.rule-tabs ::v-deep .el-tabs__item.is-active .el-badge__content.is-dot {
+  background-color: #faad14;
+  border: none;
+  box-shadow: none;
+}
+
+/* 标签内容区域 */
+.tab-content-row {
+  min-height: 300px;
+}
+
+/* 加载动画优化 */
+.tab-content-row ::v-deep .el-loading-mask {
+  border-radius: 6px;
+}
+
+.tab-content-row ::v-deep .el-loading-spinner {
+  top: 40%;
+}
+
+.tab-content-row ::v-deep .el-loading-spinner .circular {
+  width: 42px;
+  height: 42px;
+}
+
+.tab-content-row ::v-deep .el-loading-spinner .el-loading-text {
+  color: #1890ff;
+  font-size: 14px;
+  margin-top: 10px;
+}
+
+/* 天内容卡片 */
+.day-content-card {
+  border: 1px solid #e8e8e8;
+  border-radius: 6px;
+  margin-bottom: 20px;
+}
+
+.day-content-card ::v-deep .el-card__body {
+  padding: 20px;
+}
+
+/* 内容项卡片 */
+.content-item-card {
+  background-color: #fafafa;
+  padding: 16px;
+  border: 1px solid #e6e6e6;
+  border-radius: 6px;
+  margin-bottom: 16px;
+  transition: all 0.3s;
+}
+
+.content-item-card:hover {
+  border-color: #1890ff;
+  box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1);
+}
+
+/* 内容类别样式 */
+.content-type-item {
+  margin-bottom: 20px;
+}
+
+.content-type-item ::v-deep .el-form-item__label {
+  font-weight: 600;
+  line-height: 32px;
+}
+
+.content-type-item ::v-deep .el-form-item__content {
+  line-height: 32px;
+}
+
+.content-type-item ::v-deep .el-radio-group {
+  display: inline-flex;
+  flex-wrap: wrap;
+  gap: 8px 20px;
+  line-height: 32px;
+}
+
+.content-type-item ::v-deep .el-radio {
+  margin-right: 0;
+  line-height: 32px;
+}
+
+/* 表单优化 */
+.sop-form ::v-deep .el-form-item {
+  margin-bottom: 18px;
+}
+
+.sop-form ::v-deep .el-form-item__label {
+  color: #606266;
+  font-size: 14px;
+}
+
+/* 标签优化 */
+.sop-form ::v-deep .el-tag {
+  border-radius: 4px;
+  padding: 0 10px;
+  height: 28px;
+  line-height: 28px;
+}
+
+/* 输入框优化 */
+.sop-form ::v-deep .el-input__inner,
+.sop-form ::v-deep .el-textarea__inner {
+  border-radius: 4px;
+  transition: all 0.3s;
+}
+
+.sop-form ::v-deep .el-input__inner:focus,
+.sop-form ::v-deep .el-textarea__inner:focus {
+  border-color: #1890ff;
+}
+
+/* 卡片样式统一 */
+.sop-form ::v-deep .el-card {
+  border-radius: 6px;
+  overflow: hidden;
+}
+
+.sop-form ::v-deep .box-card {
+  margin-top: 12px;
+  background: #f9f9f9;
+  border: 1px solid #e8e8e8;
+}
+
+.sop-form ::v-deep .box-card .el-card__body {
+  padding: 16px;
+}
+
+/* 按钮组样式 */
+.sop-form ::v-deep .el-button-group {
+  display: flex;
+  gap: 8px;
+}
+
+/* 开关样式优化 */
+.sop-form ::v-deep .el-switch {
+  margin-right: 12px;
+}
+
+/* 提示信息样式 */
+.sop-form ::v-deep .el-form-item__content > div[style*="color: #999"] {
+  background: #f0f9ff;
+  padding: 8px 12px;
+  border-radius: 4px;
+  border-left: 3px solid #1890ff;
+  margin-top: 8px;
+}
+
+.sop-form ::v-deep .el-form-item__content > div[style*="color: #999"] i {
+  color: #1890ff;
+  margin-right: 6px;
+}
+
+/* ===== 规则内容优化 ===== */
+.rule-content-item {
+  margin-top: 20px;
+}
+
+.rule-content-item ::v-deep .el-form-item__label {
+  font-weight: 600;
+  font-size: 15px;
+  color: #333;
+}
+
+/* 内容项卡片头部 */
+.content-item-header {
+  background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%);
+  padding: 12px 16px;
+  margin: -16px -16px 16px -16px;
+  border-bottom: 2px solid #1890ff;
+  border-radius: 6px 6px 0 0;
+}
+
+.content-item-title {
+  display: flex;
+  align-items: center;
+  font-weight: 600;
+  font-size: 14px;
+  color: #333;
+}
+
+.content-item-title i {
+  margin-right: 6px;
+  color: #1890ff;
+  font-size: 16px;
+}
+
+/* 设置项卡片 */
+.setting-item-card {
+  background: #fff;
+  border: 1px solid #e0e6ed;
+  border-radius: 6px;
+  margin-bottom: 16px;
+  padding: 16px;
+  transition: all 0.3s;
+}
+
+.setting-item-card:hover {
+  border-color: #1890ff;
+  box-shadow: 0 2px 12px rgba(24, 144, 255, 0.12);
+}
+
+.setting-item-header {
+  margin: -16px -16px 16px -16px;
+  padding: 10px 16px;
+  background: #f9fafb;
+  border-bottom: 1px solid #e8e8e8;
+  border-radius: 6px 6px 0 0;
+}
+
+.setting-item-title {
+  display: flex;
+  align-items: center;
+  font-weight: 500;
+  font-size: 13px;
+  color: #606266;
+}
+
+.setting-item-title i {
+  margin-right: 6px;
+  color: #909399;
+  font-size: 14px;
+}
+
+.setting-content {
+  padding-bottom: 12px;
+}
+
+/* 内容表单统一样式 */
+.content-form ::v-deep .el-form-item__label {
+  padding-right: 8px;
+  text-align: right;
+}
+
+/* 内联表单项 */
+.inline-form-item {
+  margin-bottom: 18px;
+}
+
+.inline-form-item ::v-deep .el-form-item__content {
+  display: inline-flex;
+  align-items: center;
+  margin-left: 0 !important;
+}
+
+/* 开关表单项 */
+.switch-form-item {
+  margin-bottom: 20px;
+}
+
+.switch-form-item ::v-deep .el-form-item__label {
+  line-height: 32px;
+}
+
+.switch-form-item ::v-deep .el-form-item__content {
+  display: block;
+}
+
+.switch-form-item ::v-deep .el-switch {
+  vertical-align: middle;
+}
+
+/* 表单提示信息 */
+.form-tip-info {
+  background: #f0f9ff;
+  color: #666;
+  font-size: 13px;
+  padding: 10px 14px;
+  border-radius: 4px;
+  border-left: 3px solid #1890ff;
+  display: flex;
+  align-items: flex-start;
+  line-height: 1.6;
+  margin-top: 8px;
+}
+
+.form-tip-info i {
+  color: #1890ff;
+  margin-right: 8px;
+  margin-top: 2px;
+  flex-shrink: 0;
+}
+
+/* 内容操作按钮 */
+.content-actions {
+  display: flex;
+  gap: 12px;
+  margin-top: 12px;
+  padding: 8px 12px;
+  border-radius: 4px;
+}
+
+.content-actions .el-button {
+  margin-left: 0;
+  padding: 8px 15px;
+}
+
+.content-actions .el-button [class*="el-icon-"] {
+  margin-right: 4px;
+}
+
+/* 消息类别 Radio 组优化 */
+.sop-form ::v-deep .el-radio {
+  margin-right: 16px;
+  margin-bottom: 8px;
+}
+
+.sop-form ::v-deep .el-radio__label {
+  font-size: 13px;
+  padding-left: 8px;
+}
+
+/* Select 下拉框优化 */
+.sop-form ::v-deep .el-select {
+  margin-right: 12px;
+  margin-bottom: 8px;
+}
+
+.sop-form ::v-deep .el-select .el-input__inner {
+  border-radius: 4px;
+}
+
+/* 标签输入优化 */
+.sop-form ::v-deep .el-tag {
+  margin-right: 8px;
+  margin-bottom: 8px;
+}
+
+.sop-form ::v-deep .input-new-tag {
+  margin-right: 8px;
+  margin-bottom: 8px;
+}
+
+.sop-form ::v-deep .button-new-tag {
+  margin-bottom: 8px;
+}
+
+/* ===== 小程序、福袋、上传卡片优化 ===== */
+.miniprogram-card,
+.luckybag-card,
+.upload-card {
+  margin-top: 12px;
+  border: 1px solid #e8e8e8;
+}
+
+.miniprogram-card ::v-deep .el-card__header,
+.luckybag-card ::v-deep .el-card__header,
+.upload-card ::v-deep .el-card__header {
+  padding: 0;
+  border-bottom: none;
+}
+
+.miniprogram-card ::v-deep .el-card__body,
+.luckybag-card ::v-deep .el-card__body,
+.upload-card ::v-deep .el-card__body {
+  padding: 16px 20px;
+}
+
+.card-header-mini {
+  display: flex;
+  align-items: center;
+  font-weight: 600;
+  font-size: 14px;
+  color: #333;
+  padding: 10px 16px;
+  background: linear-gradient(135deg, #f9fafb 0%, #f0f4f8 100%);
+  border-bottom: 1px solid #e8e8e8;
+}
+
+.card-header-mini i {
+  margin-right: 8px;
+  font-size: 16px;
+  color: #1890ff;
+}
+
+.miniprogram-card .el-form-item,
+.luckybag-card .el-form-item,
+.upload-card .el-form-item {
+  margin-bottom: 16px;
+}
+
+.miniprogram-card .el-form-item:last-child,
+.luckybag-card .el-form-item:last-child,
+.upload-card .el-form-item:last-child {
+  margin-bottom: 0;
+}
+
+.miniprogram-card .el-form-item__label::before,
+.luckybag-card .el-form-item__label::before,
+.upload-card .el-form-item__label::before {
+  color: #f56c6c;
+  margin-right: 4px;
+}
+
+/* 上传组件优化 */
+.avatar-uploader ::v-deep .el-upload {
+  border: 2px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  transition: all 0.3s;
+}
+
+.avatar-uploader ::v-deep .el-upload:hover {
+  border-color: #1890ff;
+}
+
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 150px;
+  height: 150px;
+  line-height: 150px;
+  text-align: center;
+  transition: all 0.3s;
+}
+
+.avatar-uploader ::v-deep .el-upload:hover .avatar-uploader-icon {
+  color: #1890ff;
+}
+
+/* ===== 排序对话框优化 ===== */
+.sort-dialog ::v-deep .el-dialog {
+  border-radius: 8px;
+}
+
+.sort-dialog ::v-deep .el-dialog__header {
+  background: linear-gradient(135deg, #1890ff 0%, #0066cc 100%);
+  padding: 16px 20px;
+  border-radius: 8px 8px 0 0;
+}
+
+.sort-dialog ::v-deep .el-dialog__title {
+  color: #fff;
+  font-weight: 600;
+  font-size: 16px;
+}
+
+.sort-dialog ::v-deep .el-dialog__headerbtn .el-dialog__close {
+  color: #fff;
+  font-size: 18px;
+}
+
+.sort-dialog ::v-deep .el-dialog__body {
+  padding: 20px;
+}
+
+.sort-dialog-content {
+  min-height: 200px;
+}
+
+.sort-tip {
+  display: flex;
+  align-items: center;
+  padding: 10px 14px;
+  background: #f0f9ff;
+  border-left: 3px solid #1890ff;
+  border-radius: 4px;
+  margin-bottom: 20px;
+  color: #666;
+  font-size: 13px;
+}
+
+.sort-tip i {
+  color: #1890ff;
+  margin-right: 8px;
+  font-size: 16px;
+}
+
+.sort-button-group {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+  padding: 20px;
+  background: #fafafa;
+  border-radius: 6px;
+  min-height: 120px;
+}
+
+.sort-button {
+  padding: 10px 20px;
+  border-radius: 6px;
+  font-size: 14px;
+  cursor: move;
+  transition: all 0.3s;
+  border: 2px solid #e8e8e8;
+  background: #fff;
+}
+
+.sort-button:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  border-color: #1890ff;
+}
+
+.sort-button.changed {
+  background: linear-gradient(135deg, #fff1f0 0%, #ffccc7 100%);
+  border-color: #ff4d4f;
+  color: #cf1322;
+  font-weight: 600;
+}
+
+.origin-day {
+  font-size: 12px;
+  color: #999;
+  margin-left: 6px;
+  font-weight: normal;
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding-top: 10px;
+}
+
+.dialog-footer .el-button {
+  margin-left: 0;
+}
+
+/* ===== 图片和链接卡片优化 ===== */
+.image-card,
+.link-card {
+  margin-top: 12px;
+  border: 1px solid #e8e8e8;
+}
+
+.image-card ::v-deep .el-card__header,
+.link-card ::v-deep .el-card__header {
+  padding: 0;
+  border-bottom: none;
+}
+
+.image-card ::v-deep .el-card__body,
+.link-card ::v-deep .el-card__body {
+  padding: 16px 20px;
+}
+
+.image-card .el-form-item,
+.link-card .el-form-item {
+  margin-bottom: 16px;
+}
+
+.image-card .el-form-item:last-child,
+.link-card .el-form-item:last-child {
+  margin-bottom: 0;
+}
+
+.image-card .el-form-item__label::before,
+.link-card .el-form-item__label::before {
+  color: #f56c6c;
+  margin-right: 4px;
+}
+
+/* 图片上传区域 */
+.image-card ::v-deep .image-upload {
+  display: flex;
+  justify-content: center;
+  padding: 20px 0;
+}
+
+/* 链接地址区域 */
+.link-url-section {
+  margin-top: 16px;
+  padding-top: 16px;
+  border-top: 1px solid #f0f0f0;
+}
+
+.link-tip-section {
+  margin-top: 16px;
+  padding: 16px;
+  background: #fffbf0;
+  border-radius: 6px;
+  border: 1px solid #ffe7ba;
+}
+
+.link-tip-section .el-tag {
+  display: flex;
+  align-items: center;
+  padding: 10px 14px;
+  font-size: 13px;
+  line-height: 1.6;
+  border: none;
+  background: transparent;
+}
+
+.link-tip-section .el-tag i {
+  margin-right: 8px;
+  font-size: 16px;
+}
+
+/* 链接卡片输入框优化 */
+.link-card ::v-deep .el-input__inner,
+.link-card ::v-deep .el-textarea__inner {
+  border-radius: 6px;
+}
+
+.link-card ::v-deep .el-form-item__label {
+  font-weight: 500;
+  color: #606266;
+}
+
+/* 图片上传组件增强 */
+.image-card ::v-deep .el-upload {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: 2px dashed #d9d9d9;
+  border-radius: 8px;
+  transition: all 0.3s;
+}
+
+.image-card ::v-deep .el-upload:hover {
+  border-color: #1890ff;
+}
+
+.image-card ::v-deep .el-upload-list__item {
+  border-radius: 8px;
+  transition: all 0.3s;
+}
+
+.image-card ::v-deep .el-upload-list__item:hover {
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+/* ===== 消息类别和课程选择优化 ===== */
+.msg-category-item {
+  margin-bottom: 20px;
+}
+
+.msg-category-item ::v-deep .el-form-item__label {
+  font-weight: 500;
+  white-space: nowrap;
+  line-height: 32px;
+}
+
+.msg-category-item ::v-deep .el-form-item__content {
+  line-height: 32px;
+}
+
+.msg-category-item ::v-deep .el-radio-group {
+  display: inline-flex;
+  flex-wrap: wrap;
+  gap: 8px 20px;
+  line-height: 32px;
+}
+
+.msg-category-item ::v-deep .el-radio {
+  margin-right: 0;
+  margin-bottom: 0;
+  line-height: 32px;
+}
+
+/* 课程选择区域 */
+.course-select-item {
+  margin-bottom: 20px;
+}
+
+.course-select-item ::v-deep .el-form-item__label {
+  font-weight: 500;
+  white-space: nowrap;
+}
+
+.course-select-row {
+  display: flex;
+  gap: 12px;
+  flex-wrap: wrap;
+  margin-bottom: 12px;
+}
+
+.course-select-row .el-select {
+  min-width: 160px;
+  flex: 1;
+}
+
+.course-type-row {
+  display: flex;
+  gap: 12px;
+}
+
+.course-type-row .el-select {
+  width: 180px;
+}
+</style>
+<script>
+import draggable from 'vuedraggable';
+import {
+  listSopTemp,
+  getSopTemp,
+  delSopTemp,
+  addSopTemp,
+  sortDay,
+  dayListFun,
+  updateSopTemp,
+  exportSopTemp,
+  addOrUpdateSetting,
+  getSelectableRange,
+  selectRulesInfo,
+  delRules
+} from "@/api/qw/sopTemp";
+import {courseList, videoList} from "@/api/qw/sop";
+import {listToLiveNoEnd} from "@/api/live/live";
+import ImageUpload from "@/views/qw/sop/ImageUpload";
+import userVideo from "@/views/qw/userVideo/userVideo.vue";
+import {listReward} from "@/api/qw/luckyBag";
+import { getSmsTempList } from "@/api/company/companySmsTemp";
+import {
+  getRoles,
+} from "@/api/qw/sop";
+
+export default {
+  name: "updateSopTemp",
+  components: {ImageUpload, userVideo, draggable},
+  data() {
+    return {
+      queryParams1: {
+        pageNum: 1,
+        pageSize: 10,
+        dataStatus: '1',
+        name: null,
+        type: null
+      },
+      luckyBagList: [],
+      projectFrom:process.env.VUE_APP_PROJECT_FROM,
+      addTag: [{
+        addTag: [],
+        inputVisible: false,
+        inputValue: '',
+        delTag: [],
+        delTagVisible: false,
+        delTagValue: ''
+      }],
+      uploadUrl: process.env.VUE_APP_BASE_API + "/common/uploadOSS2",
+      uploadUrlByVoice: process.env.VUE_APP_BASE_API + "/common/uploadOSSByHOOKVoice",
+      //上传语音的遮罩层
+      voiceLoading: false,
+      openSort: false,
+      openSort2: false,
+      // 遮罩层
+      loading: false,
+      loading2: false,
+      loading3: false,
+      // 导出遮罩层
+      exportLoading: false,
+      roles: [],
+      // 选中数组
+      dayList: [],
+      ruleList: [],
+      ids: [],
+      // startTimeRange: [],
+      courseTypeList: ['1','2', '4','5','6', '7','8','9','10','14','15','16','11'],
+      sysFsSopWatchStatus: [],
+      //消息内容类型 企微版
+      sysQwSopContentType: [],
+      //插件版
+      sysQwSopAiContentType: [],
+
+      //类别
+      sysQwSopSettingType: [],
+      smsTemplateList: [],
+
+      courseList: [],
+      videoList: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      defaultContentType: 1,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      id: "",
+      total: 0,
+      tabIndex: null,
+      // sop模板表格数据
+      setting: [],
+      data: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 状态字典
+      statusOptions: [],
+      videoNumOptions: {
+        title: '选择视频号',
+        open: false,
+        content: null,
+        contentIndex: null,
+        setIndex: null,
+      },
+      //1 修改 2 复制 3 查看
+      formType: null,
+      // 查询参数
+      form: {
+        name: null,
+        setting: null,
+        status: "1",
+        sort: 1,
+        companyId: null,
+        gap: 1,
+      },
+      // 缓存对象用于存储预加载的数据
+      tabCache: {},
+      // 标记哪些tab已经被请求过
+      loadedTabs: new Set(),
+      // 标记正在加载的tab,避免重复请求
+      loadingTabs: new Set(),
+      // 表单校验
+      rules: {
+        name: [
+          {required: true, message: '名称不能为空', trigger: 'blur'}
+        ],
+        status: [
+          {required: true, message: '状态不能为空', trigger: 'blur'}
+        ],
+        sort: [
+          {required: true, message: '排序不能为空', trigger: 'blur'}
+        ],
+        gap: [
+          {required: true, message: '间隔天数不能为空', trigger: 'blur'}
+        ],
+      },
+      liveList: [],
+    };
+  },
+  created() {
+    getRoles().then(res => {
+      this.roles = res.data;
+    })
+    this.getDicts("sys_qwSopAi_contentType").then(response => {
+      this.sysQwSopAiContentType = response.data;
+    });
+    this.getDicts("sys_fs_sop_watch_status").then(response => {
+      this.sysFsSopWatchStatus = response.data;
+    });
+    // getSelectableRange().then(e => {
+    //   this.startTimeRange = e.data;
+    // })
+
+    this.getDicts("sys_qwSop_contentType").then(response => {
+      this.sysQwSopContentType = response.data;
+    });
+
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+
+    this.getDicts("sys_qwSop_settingType").then(response => {
+      this.sysQwSopSettingType = response.data;
+    });
+
+    courseList().then(response => {
+      this.courseList = response.list;
+    });
+
+    listToLiveNoEnd().then(response => {
+      this.liveList = response.rows;
+    })
+    listReward(this.queryParams1).then(response => {
+      this.luckyBagList = response.rows;
+    });
+
+    const id = this.$route.params && this.$route.params.id;
+    this.id = id;
+    this.formType = this.$route.params && this.$route.params.type;
+    console.info(this.form)
+    this.handleUpdate(id);
+
+    this.loadSmsTemplates();
+  },
+  methods: {
+    getLuckyBagStatus(content) {
+      const selectedLuckyBag = this.luckyBagList.find(item => item.id === content.luckyBagId);
+      if (selectedLuckyBag) {
+        content.luckyBagDataStatus = selectedLuckyBag.dataStatus; // 自动填充标题
+      } else {
+        // 若未找到对应直播间,清空标题和封面(可选)
+        content.luckyBagDataStatus = null;
+      }
+    },
+
+    // 获取短信模板信息
+    getSmsTemplateInfo(content) {
+      // 如果短信模板列表为空,先加载数据
+      if (this.smsTemplateList.length === 0) {
+        getSmsTempList().then(response => {
+          this.smsTemplateList = response.rows || response.data || [];
+          // 加载完成后再次调用自身来处理选中项
+          this.processSelectedSmsTemplate(content);
+        }).catch(error => {
+          console.error('加载短信模板失败:', error);
+          this.$message.error('加载短信模板失败');
+        });
+      } else {
+        // 直接处理选中项
+        this.processSelectedSmsTemplate(content);
+      }
+    },
+
+    // 处理选中的短信模板
+    processSelectedSmsTemplate(content) {
+      const selectedTemplate = this.smsTemplateList.find(item => item.tempId === content.smsTemplateId);
+      if (selectedTemplate) {
+        // 自动填充模板内容预览
+        this.$set(content, 'smsTemplateContent', selectedTemplate.content || selectedTemplate.templateContent || '');
+      } else {
+        // 清空相关内容
+        this.$set(content, 'smsTemplateContent', '');
+      }
+    },
+
+    // 加载短信模板列表
+    loadSmsTemplates() {
+      if (this.smsTemplateList.length > 0) {
+        return; // 如果已经有数据,不再重复加载
+      }
+
+      getSmsTempList().then(response => {
+        this.smsTemplateList = response.rows || response.data || [];
+        if (this.smsTemplateList.length === 0) {
+          this.$message.info('暂无可用的短信模板');
+        }
+      }).catch(error => {
+        console.error('加载短信模板失败:', error);
+        this.$message.error('加载短信模板失败');
+      });
+    },
+    liveChange(content) {
+      // content.liveId 是选中的直播间 ID(liveId)
+      const selectedLive = this.liveList.find(live => live.liveId === content.liveId);
+      if (selectedLive) {
+        // 从选中的直播间对象中提取标题和封面,赋值给当前内容的对应字段
+        // 假设直播间对象中标题字段为 liveTitle,封面字段为 coverImg(根据实际接口字段调整)
+        content.miniprogramTitle = selectedLive.liveName || ''; // 自动填充标题
+        content.miniprogramPicUrl = selectedLive.liveImgUrl || ''; // 自动填充封面
+      } else {
+        // 若未找到对应直播间,清空标题和封面(可选)
+        content.miniprogramTitle = '';
+        content.miniprogramPicUrl = '';
+      }
+    },
+    liveChangeContent(e){
+      //循环修改当前content下面的直播间内容
+      //并且禁用直播间编辑
+      if(!!e && !!e.setting){
+        for(let i=0;i<e.setting.length;i++){
+          if(!!e.liveId){
+             this.$set(e.setting[i], 'liveId', e.liveId);
+             this.liveChange(e.setting[i]);
+          }
+          //直播间变为不可编辑
+           this.$set(e.setting[i], 'setListDisabled', true);
+        }
+      }
+    },
+    handleClose(index, tag, content) {
+      content.addTag.splice(content.addTag.indexOf(tag), 1);
+    },
+    showInput(index) {
+      this.addTag[index].inputVisible = true;
+    },
+    handleInputConfirm(index, content) {
+      let inputValue = this.addTag[index].inputValue;
+      if (inputValue) {
+        if (!content.hasOwnProperty('addTag')) {
+          this.$set(content, 'addTag', []);
+        }
+        content.addTag.push(inputValue);
+      }
+      this.addTag[index].inputVisible = false;
+      this.addTag[index].inputValue = '';
+    },
+
+    // 检查字节长度
+    checkByteLength(content, type, isOfficial) {
+
+      if (type == 4 && isOfficial == '1')
+        for (let i = 0; i < content.setting.length; i++) {
+          const text = content.setting[i].miniprogramTitle;
+          const byteLength = this.getByteLength(text); // 获取当前字节数
+          // 如果字节数超过64,截断输入内容
+          if (byteLength > 64) {
+            this.$set(content.setting[i], 'miniprogramTitle', this.truncateTextByByteLength(text, 60));
+          }
+
+        }
+
+    },
+
+    // 计算字符串的字节数
+    getByteLength(text) {
+      return new Blob([text]).size; // 使用 Blob 计算字节数
+    },
+
+    // 根据字节数截断字符串
+    truncateTextByByteLength(text, maxByteLength) {
+      let byteLength = 0;
+      let result = "";
+
+      for (let i = 0; i < text.length; i++) {
+        const char = text[i];
+        const charByteLength = this.getByteLength(char); // 获取当前字符的字节数
+
+        // 如果加上当前字符的字节数后不超过限制,则添加到结果中
+        if (byteLength + charByteLength <= maxByteLength) {
+          result += char;
+          byteLength += charByteLength;
+        } else {
+          break; // 超过限制时停止
+        }
+      }
+
+      return result;
+    },
+
+    handleCloseDel(index, tag, content) {
+      content.delTag.splice(content.delTag.indexOf(tag), 1);
+    },
+
+    showInputDel(index) {
+      this.addTag[index].delTagVisible = true;
+    },
+    handleInputConfirmDel(index, content) {
+
+      let delTagValue = this.addTag[index].delTagValue;
+
+      if (delTagValue) {
+        if (!content.hasOwnProperty('delTag')) {
+          this.$set(content, 'delTag', []);
+        }
+        content.delTag.push(delTagValue);
+
+      }
+      this.addTag[index].delTagVisible = false;
+      this.addTag[index].delTagValue = '';
+    },
+    saveSorts() {
+      let list = this.dayList.filter(e => e.dayNum != e.newDay).map(e => {
+        return {dayNum: e.newDay, id: e.id}
+      })
+      this.loading3 = true;
+      sortDay(list).then(e => {
+        window.location.reload();
+      })
+    },
+    saveSortsRules() {
+      this.$set(this.setting[this.tabIndex], 'content', this.ruleList);
+      this.openSort2 = false;
+    },
+    autoSortsRules() {
+      this.ruleList = this.ruleList.sort((a, b) => {
+        // 将 time 字段转换为 Date 对象进行比较
+        const timeA = new Date(`1970-01-01T${a.time}Z`);
+        const timeB = new Date(`1970-01-01T${b.time}Z`);
+
+        // 比较时间顺序
+        return timeA - timeB; // 升序排序
+      });
+    },
+    openUpdateDaySorts() {
+      dayListFun(this.id).then(response => {
+        response.data.forEach((item) => item.newDay = item.dayNum);
+        this.dayList = response.data;
+        if (this.dayList == null || this.dayList.length == 0) {
+          this.$message.error("暂无天数")
+        } else {
+          this.openSort = true;
+        }
+      })
+    },
+    openUpdateSorts() {
+      this.ruleList = JSON.parse(JSON.stringify(this.setting[this.tabIndex].content));
+      if (this.ruleList == null || this.ruleList.length == 0) {
+        this.$message.error("暂无规则")
+      } else {
+        this.openSort2 = true;
+      }
+    },
+    onDragEndDay() {
+      this.dayList.forEach((item, index) => {
+        item.newDay = (this.form.gap * index) + 1;
+      })
+      this.$forceUpdate()
+    },
+    onDragEnd() {
+      // 更新排序值
+      // this.setting[this.tabIndex].content.forEach((item, index) => {
+      //   item.sorts = index;
+      // });
+    },
+    // 切换标签保存数据
+    async leave(index, oldIndex) {
+      const newData = this.setting[index]
+      if (newData.dayNum && newData.id) {
+        // 检查是否已经有缓存数据
+        if (this.tabCache[newData.id]) {
+          // 使用缓存数据,无需loading
+          this.applyCachedData(index, newData);
+        } else {
+          // 没有缓存,需要请求数据
+          this.loading = true;
+          try {
+            const res = await selectRulesInfo(newData.id);
+            // 缓存数据
+            this.tabCache[newData.id] = res.data;
+            this.loadedTabs.add(index);
+            this.applyDataToTab(index, newData, res.data);
+          } catch (error) {
+            console.error("获取规则信息失败:", error);
+          } finally {
+            this.loading = false;
+          }
+        }
+
+        // 预加载下一个tab的数据
+        this.preloadNextTab(index);
+      }
+    },
+
+    // 应用缓存数据到tab
+    applyCachedData(index, newData) {
+      const cachedData = this.tabCache[newData.id];
+      this.$nextTick(() => {
+        this.videoList = [];
+        this.addTag = [];
+        this.$set(this.setting, index, {
+          ...newData,
+          voice: cachedData.voice,
+          content: cachedData.list || [{type: this.defaultContentType, contentType: '1', setting: [{contentType: '1', value: ""}]}]
+        });
+
+        for (let j = 0; j < this.setting[index].content.length; j++) {
+          if (this.setting[index].content[j].addTag != null) {
+            this.setting[index].content[j].addTag = JSON.parse(this.setting[index].content[j].addTag)
+          }
+          if (this.setting[index].content[j].delTag != null) {
+            this.setting[index].content[j].delTag = JSON.parse(this.setting[index].content[j].delTag)
+          }
+          this.videoList.push([])
+          this.addTag.push({
+            addTag: [],
+            inputVisible: false,
+            inputValue: '',
+            delTag: [],
+            delTagVisible: false,
+            delTagValue: ''
+          })
+          if (this.setting[index].content[j].type == 2) {
+            if (this.setting[index].content[j].hasOwnProperty('courseId')) {
+              this.courseChange(this.setting[index].content[j], index, j);
+            }
+          }
+        }
+      });
+    },
+
+    // 应用新获取的数据到tab
+    applyDataToTab(index, newData, data) {
+      this.$nextTick(() => {
+        this.videoList = [];
+        this.addTag = [];
+        this.$set(this.setting, index, {
+          ...newData,
+          voice: data.voice,
+          content: data.list || [{type: this.defaultContentType, contentType: '1', setting: [{contentType: '1', value: ""}]}]
+        });
+
+        for (let j = 0; j < this.setting[index].content.length; j++) {
+          if (this.setting[index].content[j].addTag != null) {
+            this.setting[index].content[j].addTag = JSON.parse(this.setting[index].content[j].addTag)
+          }
+          if (this.setting[index].content[j].delTag != null) {
+            this.setting[index].content[j].delTag = JSON.parse(this.setting[index].content[j].delTag)
+          }
+          this.videoList.push([])
+          this.addTag.push({
+            addTag: [],
+            inputVisible: false,
+            inputValue: '',
+            delTag: [],
+            delTagVisible: false,
+            delTagValue: ''
+          })
+          if (this.setting[index].content[j].type == 2) {
+            if (this.setting[index].content[j].hasOwnProperty('courseId')) {
+              this.courseChange(this.setting[index].content[j], index, j);
+            }
+          }
+        }
+      });
+    },
+
+    // 预加载下一个tab的数据
+    async preloadNextTab(currentIndex) {
+      const nextIndex = parseInt(currentIndex) + 1;
+      // 检查是否有下一个tab
+      if (nextIndex >= this.setting.length) {
+        return;
+      }
+
+      const nextTabData = this.setting[nextIndex];
+      // 只预加载已存在的tab(有id的),跳过新建的tab
+      if (!nextTabData.id || !nextTabData.dayNum) {
+        return;
+      }
+
+      // 如果已经加载过或正在加载,则跳过
+      if (this.loadedTabs.has(nextIndex) || this.loadingTabs.has(nextIndex) || this.tabCache[nextTabData.id]) {
+        return;
+      }
+
+      // 标记为正在加载
+      this.loadingTabs.add(nextIndex);
+
+      try {
+        const res = await selectRulesInfo(nextTabData.id);
+        // 缓存数据
+        this.tabCache[nextTabData.id] = res.data;
+        this.loadedTabs.add(nextIndex);
+      } catch (error) {
+        console.error("预加载tab数据失败:", error);
+      } finally {
+        // 移除加载标记
+        this.loadingTabs.delete(nextIndex);
+      }
+    },
+    save() {
+      let data = this.setting[this.tabIndex];
+      let check = this.checkData(data);
+      if (!check) {
+        return;
+      }
+      for (let j = 0; j < data.content.length; j++) {
+
+        //如果不是每天第一条,全刷为非官方
+        if (j!==0){
+          data.content[j].isOfficial=0;
+        }
+
+        if (data.content[j].type != 4 && data.content[j].type != 5) {
+          for (let k = 0; k < data.content[j].setting.length; k++) {
+            if (data.content[j].setting[k].contentType == 4 && (data.content[j].setting[k].miniprogramTitle != null || data.content[j].setting[k].miniprogramTitle != "")) {
+              data.content[j].setting[k].miniprogramTitle = this.truncateTextByByteLength(data.content[j].setting[k].miniprogramTitle, 60)
+            }
+            // 处理短信模板数据映射
+            if (data.content[j].setting[k].contentType == 21 && data.content[j].setting[k].smsTemplateId) {
+              // 根据选中的短信模板ID查找对应的模板信息
+              const selectedTemplate = this.smsTemplateList.find(template => template.tempId === data.content[j].setting[k].smsTemplateId);
+              if (selectedTemplate) {
+                // 设置短信模板的相关字段
+                this.$set(data.content[j].setting[k], 'smsTemplateCode', selectedTemplate.tempCode || '');
+                this.$set(data.content[j].setting[k], 'smsTemplateTitle', selectedTemplate.title || '');
+              }
+            }
+
+          }
+        }
+      }
+
+      let index = 0;
+      const dataList = data.content.map(e => {
+        e.name = data.name;
+        e.tempId = this.id;
+        e.sorts = Number(this.tabIndex);
+        e.dayNum = (this.form.gap * e.sorts) + 1;
+        e.sorts = index++;
+        data.dayNum = e.dayNum;
+        e.contentType = e.type;
+        e.settingList = e.setting;
+        e.settingList.forEach(e => e.content = JSON.stringify(e));
+        if (e.addTag != null) {
+          e.addTag = JSON.stringify(e.addTag);
+        }
+        if (e.delTag != null) {
+          e.delTag = JSON.stringify(e.delTag);
+        }
+        return e;
+      })
+      data.tempId = this.id;
+      data.list = dataList;
+      this.loading = true;
+      addOrUpdateSetting(data).then(e => {
+        this.$message.success(e.msg)
+        let content = this.setting[this.tabIndex].content;
+        for (let j = 0; j < content.length; j++) {
+          if (content[j].addTag != null) {
+            content[j].addTag = JSON.parse(content[j].addTag);
+          }
+          if (content[j].delTag != null) {
+            content[j].delTag = JSON.parse(content[j].delTag);
+          }
+        }
+        this.loading = false;
+        // 使用Vue.set确保响应式更新
+        this.$set(this.setting, this.tabIndex, {
+          ...this.setting[this.tabIndex],
+          id: e.data.id,
+        });
+
+        // 更新缓存中的数据
+        const currentTabId = this.setting[this.tabIndex].id;
+        if (currentTabId) {
+          // 重新获取最新数据并更新缓存
+          selectRulesInfo(currentTabId).then(res => {
+            this.tabCache[currentTabId] = res.data;
+            this.loadedTabs.add(parseInt(this.tabIndex));
+          }).catch(error => {
+            console.error("更新缓存失败:", error);
+          });
+        }
+      }).catch(() => {
+        this.loading = false;
+      });
+    },
+    checkData(data) {
+      if (this.form.sendType != 4) {
+
+        for (let j = 0; j < data.content.length; j++) {
+          if (data.name == null || data.name == "") {
+            this.$message.error("内容名称不能为空")
+            return false;
+          }
+          if (data.content[j].time == null || data.content[j].time == "") {
+            this.$message.error("时间不能为空")
+            return false;
+          }
+
+          if (data.content[j].type == 20){
+            if (data.content[j].liveId == null || data.content[j].liveId == "") {
+              this.$message.error("请选择直播间")
+              return false;
+            }
+          }
+
+          if (data.content[j].type == 2) {
+            if (data.content[j].courseId == null || data.content[j].courseId == "") {
+              this.$message.error("请选择课程")
+              return false;
+            }
+            if (data.content[j].videoId == null || data.content[j].videoId == "") {
+              this.$message.error("请选择课节")
+              return false;
+            }
+            if (data.content[j].courseType == null || data.content[j].courseType === "") {
+              this.$message.error("请选择消息类型")
+              return false;
+            }
+          }
+
+          if (data.content[j].type != 4 && data.content[j].type != 5) {
+            for (let k = 0; k < data.content[j].setting.length; k++) {
+              if (data.content[j].setting[k].contentType == 1 && (data.content[j].setting[k].value == null || data.content[j].setting[k].value == "")) {
+                this.$message.error("内容不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 2 && (data.content[j].setting[k].imgUrl == null || data.content[j].setting[k].imgUrl == "")) {
+                this.$message.error("图片不能为空")
+                return false;
+              }
+              if ((data.content[j].setting[k].contentType == 3 || data.content[j].setting[k].contentType == 9 || data.content[j].setting[k].contentType == 10 || data.content[j].setting[k].contentType == 17|| data.content[j].setting[k].contentType == 23) && (data.content[j].setting[k].linkTitle == null || data.content[j].setting[k].linkTitle == "")) {
+                this.$message.error("链接标题不能为空")
+                return false;
+              }
+              if ((data.content[j].setting[k].contentType == 3 || data.content[j].setting[k].contentType == 9 || data.content[j].setting[k].contentType == 10|| data.content[j].setting[k].contentType == 17|| data.content[j].setting[k].contentType == 23) && (data.content[j].setting[k].linkDescribe == null || data.content[j].setting[k].linkDescribe == "")) {
+                this.$message.error("链接描述不能为空")
+                return false;
+              }
+              if ((data.content[j].setting[k].contentType == 3 || data.content[j].setting[k].contentType == 9 || data.content[j].setting[k].contentType == 10|| data.content[j].setting[k].contentType == 17|| data.content[j].setting[k].contentType == 23) && (data.content[j].setting[k].linkImageUrl == null || data.content[j].setting[k].linkImageUrl == "")) {
+                this.$message.error("链接图片不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 3 && data.content[j].setting[k].type == 1 && (data.content[j].setting[k].linkUrl == null || data.content[j].setting[k].linkUrl == "")) {
+                this.$message.error("链接地址不能为空")
+                return false;
+              }
+
+              if ((data.content[j].setting[k].contentType == 4 || data.content[j].setting[k].contentType == 10 || data.content[j].setting[k].contentType == 17 || data.content[j].setting[k].contentType == 23) && (data.content[j].setting[k].miniprogramTitle == null || data.content[j].setting[k].miniprogramTitle == "")) {
+                this.$message.error("小程序消息标题不能为空")
+                return false;
+              }
+              if ((data.content[j].setting[k].contentType == 4 || data.content[j].setting[k].contentType == 10 || data.content[j].setting[k].contentType == 17 || data.content[j].setting[k].contentType == 23) && data.content[j].isOfficial !== '1' && (data.content[j].setting[k].miniprogramPicUrl == null || data.content[j].setting[k].miniprogramPicUrl == "")) {
+                this.$message.error("小程序封面地址不能为空")
+                return false;
+              }
+
+              if (data.content[j].setting[k].contentType == 10 && data.content[j].isOfficial !== '1' && (data.content[j].setting[k].miniprogramPage == null || data.content[j].setting[k].miniprogramPage == "")) {
+                this.$message.error("小程序page地址不能为空")
+                return false;
+              }
+
+              if (data.content[j].setting[k].contentType == 5 && (data.content[j].setting[k].fileUrl == null || data.content[j].setting[k].fileUrl == "")) {
+                this.$message.error("文件不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 6 && (data.content[j].setting[k].videoUrl == null || data.content[j].setting[k].videoUrl == "")) {
+                this.$message.error("视频不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 7 && (data.content[j].setting[k].value == null || data.content[j].setting[k].value == "")) {
+                this.$message.error("语音文本不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 8 && (data.content[j].setting[k].url == null || data.content[j].setting[k].url == "")) {
+                this.$message.error("视频号信息不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 21 && (data.content[j].setting[k].smsTemplateId == null || data.content[j].setting[k].smsTemplateId == "")) {
+                this.$message.error("短信模板不能为空")
+                return false;
+              }
+            }
+          } else if (data.content[j].type == 4) {
+            if (data.content[j].aiTouch == null || data.content[j].aiTouch == '') {
+              this.$message.error("AI触达不能为空")
+              return false;
+            }
+          }
+        }
+      } else {
+        for (let day of this.setting) {
+          for (let content of day.content) {
+            for (let set of content.setting) {
+              // if (!set.intervalTime) {
+              //   this.$message.error("客服时间不能为空");
+              //   return false;
+              // }
+                // 第一天校验intervalTime,第二天及以后校验time
+                if (day.dayNum == 1) {
+                    if (!set.intervalTime) {
+                        this.$message.error("客服时间不能为空");
+                        return false;
+                    }
+                } else {
+                    if (!set.time) {
+                        this.$message.error("触发时间不能为空");
+                        return false;
+                    }
+                }
+                // 校验发送用户类型
+                if (!set.onlyUnregistered) {
+                    this.$message.error("发送用户类型不能为空");
+                    return false;
+                }
+              if (set.contentType == 1 && (set.value == null || set.value == "")) {
+                this.$message.error("内容不能为空")
+                return false;
+              }
+              if (set.contentType == 2 && (set.imgUrl == null || set.imgUrl == "")) {
+                this.$message.error("图片不能为空")
+                return false;
+              }
+              if ((set.contentType == 3 || set.contentType == 9) && (set.linkTitle == null || set.linkTitle == "")) {
+                this.$message.error("链接标题不能为空")
+                return false;
+              }
+              if ((set.contentType == 3 || set.contentType == 9) && (set.linkDescribe == null || set.linkDescribe == "")) {
+                this.$message.error("链接描述不能为空")
+                return false;
+              }
+              if ((set.contentType == 3 || set.contentType == 9) && (set.linkImageUrl == null || set.linkImageUrl == "")) {
+                this.$message.error("链接图片不能为空")
+                return false;
+              }
+              if (set.contentType == 3 && set.type == 1 && (set.linkUrl == null || set.linkUrl == "")) {
+                this.$message.error("链接地址不能为空")
+                return false;
+              }
+
+              if (set.contentType == 4 && (set.miniprogramTitle == null || set.miniprogramTitle == "")) {
+                this.$message.error("小程序消息标题不能为空")
+                return false;
+              }
+              if (set.contentType == 4 && (set.miniprogramPicUrl == null || set.miniprogramPicUrl == "")) {
+                this.$message.error("小程序封面地址不能为空")
+                return false;
+              }
+
+              if (set.contentType == 5 && (set.fileUrl == null || set.fileUrl == "")) {
+                this.$message.error("文件不能为空")
+                return false;
+              }
+              if (set.contentType == 6 && (set.videoUrl == null || set.videoUrl == "")) {
+                this.$message.error("视频不能为空")
+                return false;
+              }
+              if (set.contentType == 7 && (set.value == null || set.value == "")) {
+                this.$message.error("语音文本不能为空")
+                return false;
+              }
+              if (set.contentType == 8 && (set.url == null || set.url == "")) {
+                this.$message.error("视频号信息不能为空")
+                return false;
+              }
+              if (set.contentType == 21 && (set.smsTemplateId == null || set.smsTemplateId == "")) {
+                this.$message.error("短信模板不能为空")
+                return false;
+              }
+            }
+          }
+        }
+      }
+
+      return true;
+    },
+    tabClick(obj) {
+
+    },
+    addSetList(contentIndex, content) {
+      if (this.form.sendType == 1) {
+        for (let i = 0; i < content.length; i++) {
+          if (!content[i].setting) {
+            content[i].setting = []; // 初始化 setting 数组如果不存在
+          }
+
+          if (content[i].setting.length > 9) {
+            return this.$message.error("因为企微接口限制,企微模板一条消息只能设置最多~9个内容!!")
+          } else {
+
+            // 动态生成新的设置项
+            const newSetting = content[i].setting.some(item => item.contentType === '1')
+              ? {contentType: '2', imgUrl: ''}  // 如果存在 contentType = '1',添加 contentType = '2'
+              : {contentType: '1', value: ''};  // 如果不存在 contentType = '1',添加 contentType = '1'
+
+            // 将新设置项添加到 content[i].setting 数组中
+            content[i].setting.push(newSetting);
+          }
+
+        }
+      } else {
+        if (!content[contentIndex].setting) {
+          content[contentIndex].setting = []; // 初始化 setting 数组如果不存在
+        }
+
+        const newSetting = {
+          contentType: '1',
+          value: '',
+          onlyUnregistered: '3',
+        };
+        if (this.form.sendType === 4) {
+          for (let i = 0; i < content.length; i++) {
+            if (content[i].setting.length < 5) {
+              // 将新设置项添加到 content.setting 数组中
+              content[contentIndex].setting.push(newSetting);
+            } else {
+              return this.$message.error("因为要求限制,新课对话模板一条消息只能设置最多~5个内容!!")
+            }
+          }
+        } else {
+          // 将新设置项添加到 content.setting 数组中
+          content[contentIndex].setting.push(newSetting);
+        }
+      }
+
+
+    },
+    delSetList(index, contentIndex, setIndex) {
+      this.setting[index].content[contentIndex].setting.splice(setIndex, 1)
+
+    },
+
+    addContent(index) {
+      if (this.setting[index].content.length > 0 && this.form.sendType == 1) {
+        return this.$message.error("因为企微接口限制,企微模板一天只能设置一条消息~")
+      } else {
+        const sourceContent = this.setting[index].content[0];
+        const newContent = {
+          type: this.defaultContentType,
+          contentType: 1,
+          setting: [{contentType: '1', value: ""}],
+          // 复制课程相关字段,如 courseId、videoId,根据实际数据结构补充
+          courseId: sourceContent.courseId,
+          videoId: sourceContent.videoId,
+          courseType: sourceContent.courseType,
+          isAtAll: '0',
+        };
+        this.setting[index].content.push(newContent);
+        this.videoList.push([]);
+        this.addTag.push({
+          addTag: [],
+          inputVisible: false,
+          inputValue: '',
+          delTag: [],
+          delTagVisible: false,
+          delTagValue: ''
+        });
+        // 如果有视频列表关联,也可同步处理 videoList,根据 courseId 重新请求或复制已有视频数据
+        if (sourceContent.courseId) {
+          this.courseUpdate(newContent, index, this.setting[index].content.length - 1);
+        }
+      }
+    },
+    delContent(index, contentIndex) {
+      this.setting[index].content.splice(contentIndex, 1)
+      this.videoList.splice(contentIndex, 1)
+      this.addTag.splice(contentIndex, 1)
+    },
+    addSetting() {
+      // 当 sendType == 4 时,限制最多添加7天
+      if (this.form.sendType == 4 && this.setting.length >= 7) {
+        this.$message.warning('新客对话消息,最多只能添加7天');
+        return;
+      }
+
+      let content = [{type: this.defaultContentType, contentType: '1', setting: [{contentType: '1', value: "",}],isAtAll: '0'}];
+      if (this.form.sendType == 4) {
+        content = [{type: 2, contentType: '1', setting: [{contentType: '1', value: "", onlyUnregistered: '3'}]}];
+        content[0].setting[0].intervalTime = 5;
+      }
+      this.setting.push({
+        name: null,
+        dayNum: (this.form.gap * this.setting.length) + 1,
+        content
+      })
+
+    },
+    delSetting(index) {
+      const newData = this.setting[index]
+      if (newData.id && newData.dayNum) {
+        this.$confirm('此操作将永久删除, 是否继续?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          delRules(newData.id, newData.name, newData.dayNum).then(res => {
+            this.del(index);
+          });
+        });
+      } else {
+        this.del(index);
+      }
+
+    },
+    del(index) {
+      if (index == 0) {
+        this.tabIndex = "0";
+      } else {
+        this.tabIndex = (index - 1) + "";
+      }
+      this.setting.splice(index, 1)
+
+    },
+    handleClosegroupUser(list) {
+      const index = this.userSelectList.findIndex(t => t === list);
+      if (index !== -1) {
+        this.userSelectList.splice(index, 1);
+      }
+
+    },
+    //选择视频号
+    hanldeSelectVideoNum(content, index, contentIndex, setIndex) {
+      this.videoNumOptions.content = content;
+      this.videoNumOptions.contentIndex = contentIndex;
+      this.videoNumOptions.setIndex = setIndex;
+      this.videoNumOptions.open = true;
+
+    },
+
+    qwUserVideoResult(val) {
+
+      // 根据选中的内容,将返回的数据更新到相应的表单项
+      const content = this.videoNumOptions.content;
+      const setList = content.setting[this.videoNumOptions.setIndex];
+
+      setList.nickname = val.nickname;
+      setList.avatar = val.avatar;
+      setList.coverUrl = val.coverUrl;
+      setList.thumbUrl = val.thumbUrl;
+      setList.desc = val.desc;
+      setList.url = val.url;
+      setList.extras = val.extras;
+      setList.videoId = val.id;
+
+      this.videoNumOptions.open = false;
+
+    },
+    //首次刷新
+    courseChange(content, index, countIndex) {
+
+      this.courseUpdate(content, index, countIndex);
+    },
+
+    //修改
+    courseChangeUpdate(content, index, countIndex) {
+
+      this.$set(content, 'videoId', null);
+      // 查找选中的课程对应的 label
+      const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
+      for (let i = 0; i < content.setting.length; i++) {
+
+
+        this.$set(content.setting[i], 'linkTitle', null);
+        this.$set(content.setting[i], 'linkDescribe', null);
+        this.$set(content.setting[i], 'linkImageUrl', null);
+
+
+        //如果是链接的才上
+        if (selectedCourse && content.type == 2 && content.courseId != null) {
+          //响应式直接给链接的标题/封面上值
+
+          if (content.setting[i].contentType == 3 || content.setting[i].contentType == 9) {
+            this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
+            this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+          }
+          if (content.setting[i].contentType == 4 || content.setting[i].contentType == 10 || content.setting[i].contentType == 17  || content.setting[i].contentType == 23) {
+            this.$set(content.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+          }
+
+
+        }
+      }
+
+
+      this.courseUpdate(content, index, countIndex);
+    },
+    handleInputVideoText(value, content) {
+      // 允许的字符:中文、英文(大小写)、数字和指定标点符号(,。!?)
+      const regex = /^[\u4e00-\u9fa5,。!?,!?]+$/;
+
+      // 删除不符合条件的字符
+      const filteredValue = value.split('').filter(char => regex.test(char)).join('');
+
+      this.$set(content, 'value', filteredValue);
+
+    },
+    courseUpdate(content, index, countIndex) {
+      videoList(content.courseId).then(response => {
+        // this.$set(this.videoList, countIndex, response.list); // 响应式设置videoList
+        this.videoList.splice(countIndex, 1, response.list);
+
+      });
+
+    },
+    toggleSalesCall(itemIndex, contentIndex, setIndex) {
+      // 获取目标对象
+      const setItem = this.setting[itemIndex].content[contentIndex].setting[setIndex];
+      const salesCall = '#销售称呼#';
+      const refKey = `textarea-${itemIndex}-${contentIndex}-${setIndex}`;
+      const textarea = this.$refs[refKey][0]?.$refs?.textarea;
+
+      if (!textarea) return;
+
+      // 获取光标位置
+      const cursorPosition = textarea.selectionStart;
+
+      if (setItem.isSalesCallAdded) {
+        // 移除所有标签
+        setItem.value = setItem.value.replace(new RegExp(salesCall, 'g'), '');
+      } else {
+        // 插入到光标位置
+        setItem.value =
+          setItem.value.slice(0, cursorPosition) +
+          salesCall +
+          setItem.value.slice(cursorPosition);
+      }
+
+      // 切换状态
+      setItem.isSalesCallAdded = !setItem.isSalesCallAdded;
+
+      // 保持光标位置
+      this.$nextTick(() => {
+        const newCursorPos = cursorPosition + (setItem.isSalesCallAdded ? salesCall.length : 0);
+        textarea.setSelectionRange(newCursorPos, newCursorPos);
+      });
+    },
+    toggleUserNameCall(itemIndex, contentIndex, setIndex) {
+      // 获取目标对象
+      const setItem = this.setting[itemIndex].content[contentIndex].setting[setIndex];
+      const salesCall = '#客户称呼#';
+      const refKey = `textarea-${itemIndex}-${contentIndex}-${setIndex}`;
+      const textarea = this.$refs[refKey][0]?.$refs?.textarea;
+
+      if (!textarea) return;
+
+      // 获取光标位置
+      const cursorPosition = textarea.selectionStart;
+
+      if (setItem.isUserNameCallAdded) {
+        // 移除所有标签
+        setItem.value = setItem.value.replace(new RegExp(salesCall, 'g'), '');
+      } else {
+        // 插入到光标位置
+        setItem.value =
+          setItem.value.slice(0, cursorPosition) +
+          salesCall +
+          setItem.value.slice(cursorPosition);
+      }
+
+      // 切换状态
+      setItem.isUserNameCallAdded = !setItem.isUserNameCallAdded;
+
+      // 保持光标位置
+      this.$nextTick(() => {
+        const newCursorPos = cursorPosition + (setItem.isUserNameCallAdded ? salesCall.length : 0);
+        textarea.setSelectionRange(newCursorPos, newCursorPos);
+      });
+    },
+    handleKeydown(event, itemIndex, contentIndex, setIndex) {
+
+      const setItem = this.setting[itemIndex].content[contentIndex].setting[setIndex];
+      const refKey = `textarea-${itemIndex}-${contentIndex}-${setIndex}`;
+      const textarea = this.$refs[refKey][0]?.$refs?.textarea;
+
+      if (!textarea) return;
+
+      const tags = ['#销售称呼#', '#客户称呼#'];
+      const key = event.key;
+      const value = setItem.value;
+      const cursorPosition = textarea.selectionStart;
+
+      if (key === 'Backspace' || key === 'Delete') {
+        tags.forEach(tag => {
+          let start, end;
+
+          // Backspace 处理
+          if (key === 'Backspace' && cursorPosition >= tag.length) {
+            start = cursorPosition - tag.length;
+            if (value.slice(start, cursorPosition) === tag) {
+              event.preventDefault();
+              setItem.value = value.slice(0, start) + value.slice(cursorPosition);
+              if (tag === '#销售称呼#') setItem.isSalesCallAdded = false;
+              this.$nextTick(() => textarea.setSelectionRange(start, start));
+            }
+          }
+
+          // Delete 处理
+          if (key === 'Delete') {
+            end = cursorPosition + tag.length;
+            if (value.slice(cursorPosition, end) === tag) {
+              event.preventDefault();
+              setItem.value = value.slice(0, cursorPosition) + value.slice(end);
+              if (tag === '#销售称呼#') setItem.isSalesCallAdded = false;
+            }
+          }
+        });
+      }
+    },
+    handleContentTypeChange(content, index, countIndex, setIndex, item, fieldName, val) {
+
+      //消息类别是直播间&&选择了发送直播间
+
+      if(content.type == 20 && val == 12){
+          //如果选择了直播间 直接赋值
+          if(!!content.liveId){
+             this.$set(content.setting[setIndex], 'liveId', content.liveId);
+             this.liveChange(content.setting[setIndex]);
+          }
+          //直播间变为不可编辑
+          this.$set(content.setting[setIndex], 'setListDisabled', true);
+      }else{
+        //取消禁用
+        for (let i = 0; i < content.setting.length; i++) {
+            this.$set(content.setting[i], 'setListDisabled', true);
+        }
+      }
+
+      //如果是链接的才上
+      if (content.courseId != null && content.type == 2) {
+        const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
+
+        for (let i = 0; i < content.setting.length; i++) {
+          //响应式直接给链接的标题/封面上值
+          if (selectedCourse && content.type == 2 && content.courseId != null) {
+            if (content.setting[i].contentType == 3 || content.setting[i].contentType == 9) {
+              this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
+              this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+            }
+            if ((content.setting[i].contentType == 4 || content.setting[i].contentType == 10 || content.setting[i].contentType == 17 || content.setting[i].contentType == 23) && (content.isOfficial == '0' || content.isOfficial == null)) {
+              this.$set(content.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+            }
+
+          }
+
+        }
+
+      }
+      if (content.videoId != null && content.type == 2) {
+        // 查找选中的课节对应的 label
+        const selectedVideo = this.videoList[countIndex].find(course => parseInt(course.dictValue) === content.videoId);
+
+        for (let i = 0; i < content.setting.length; i++) {
+          //响应式直接给链接的描述上值
+          if (selectedVideo && content.type == 2 && content.videoId != null) {
+
+            if (content.setting[i].contentType == 3 || content.setting[i].contentType == 9) {
+              this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+
+              if (this.projectFrom == 'sxjz' && selectedVideo.dictImgUrl) {
+                this.$set(content.setting[i], 'linkImageUrl', selectedVideo.dictImgUrl);
+              }
+
+            }
+            if (content.setting[i].contentType == 4 || content.setting[i].contentType == 10 || content.setting[i].contentType == 17 || content.setting[i].contentType == 23) {
+              this.$set(content.setting[i], 'miniprogramTitle', this.truncateTextByByteLength(selectedVideo.dictLabel, 60));
+
+              if (this.projectFrom == 'sxjz' && selectedVideo.dictImgUrl) {
+                this.$set(content.setting[i], 'miniprogramPicUrl', selectedVideo.dictImgUrl);
+              }
+
+            }
+          }
+        }
+      }
+      if(countIndex == 0 && setIndex == 0){
+        this.updateAll(setIndex, item, fieldName, val);
+        for (let index in item.content) {
+          if(index != 0){
+            this.handleContentTypeChange(item.content[index], 0, index, 0, item, fieldName, val);
+          }
+        }
+      }
+    },
+    videoIdChange(content, index, countIndex) {
+
+      //选择了课程小节则 默认绑上
+      let selectedVideo;
+      // 查找选中的课程对应的 label
+      if (content.videoId != null) {
+        selectedVideo = this.videoList[countIndex].find(course => parseInt(course.dictValue) === content.videoId);
+      }
+      for (let i = 0; i < content.setting.length; i++) {
+        //课程消息-文本内容
+        if (content.setting[i].contentType == 1 && content.type == 2) {
+          this.$set(content.setting[i], 'isBindUrl', '2');
+        }
+        //如果是链接的才上
+        if (selectedVideo && content.type == 2 && content.videoId != null) {
+
+          if (content.setting[i].contentType == 3 || content.setting[i].contentType == 9) {
+            this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+
+            if (this.projectFrom == 'sxjz' && selectedVideo.dictImgUrl) {
+              this.$set(content.setting[i], 'linkImageUrl', selectedVideo.dictImgUrl);
+            }
+
+          }
+
+          if (content.setting[i].contentType == 4 || content.setting[i].contentType == 10 || content.setting[i].contentType == 17 || content.setting[i].contentType == 23 ) {
+            this.$set(content.setting[i], 'miniprogramTitle', this.truncateTextByByteLength(selectedVideo.dictLabel, 60));
+
+            if (this.projectFrom == 'sxjz' && selectedVideo.dictImgUrl) {
+              this.$set(content.setting[i], 'miniprogramPicUrl', selectedVideo.dictImgUrl);
+            }
+
+          }
+
+
+        }
+      }
+
+    },
+    //上传文件
+    handleAvatarSuccessFile(res, file, content) {
+      if (res.code === 200) {
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'fileUrl', res.url);
+      } else {
+        this.msgError(res.msg);
+      }
+
+    },
+    beforeAvatarUploadFile(file) {
+      const isLt1M = file.size / 1024 / 1024 < 10;
+      if (!isLt1M) {
+        this.$message.error('上传大小不能超过 10MB!');
+      }
+
+      return isLt1M;
+    },
+
+    //下载文件
+    downloadUrl(materialUrl) {
+      // 直接返回文件 URL
+      return materialUrl;
+    },
+
+    handleAvatarSuccessVideo(res, file, content) {
+      if (res.code == 200) {
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'videoUrl', res.url);
+      } else {
+        this.msgError(res.msg);
+      }
+
+    },
+    beforeAvatarUploadVideo(file) {
+      const isLt30M = file.size / 1024 / 1024 < 10;
+      const isMP4 = file.type === 'video/mp4';
+
+      if (!isMP4) {
+        this.$message.error('仅支持上传 MP4 格式的视频文件!');
+        return false;
+      }
+
+      if (!isLt30M) {
+        this.$message.error('上传大小不能超过 10MB!');
+        return false;
+      }
+
+      return true;
+    },
+
+    handleAvatarSuccessVoice(res, file, content) {
+      if (res.code == 200) {
+        // 创建 Audio 对象加载音频
+        const audio = new Audio(res.mp3Url);
+        audio.addEventListener('loadedmetadata', () => {
+          // 获取音频时长
+          this.$set(content, 'voiceDuration', Math.ceil(audio.duration));
+        });
+        // 使用 $set 确保响应式更新
+        this.$set(content, 'voiceUrl', res.silkUrl);
+        this.$set(content, 'mp3Url', res.mp3Url);
+      } else {
+        this.msgError(res.msg);
+      }
+      this.voiceLoading = false;
+
+    },
+    beforeAvatarUploadVoice(file) {
+      return new Promise((resolve, reject) => {
+        const isLt10M = file.size / 1024 / 1024 < 10; // 假设语音文件大小限制为10MB
+        const isVoiceType = ['audio/mp3', 'audio/mpeg', 'audio/wav', 'audio/x-wav'].includes(file.type);
+
+        if (!isVoiceType) {
+          this.$message.error('仅支持上传 MP3, WAV, X-WAV 格式的语音文件!');
+          return reject(false); // 不允许继续上传
+        }
+
+        if (!isLt10M) {
+          this.$message.error('上传大小不能超过 10MB!');
+          return reject(false); // 不允许继续上传
+        }
+
+        // 使用 FileReader 读取文件
+        const reader = new FileReader();
+        reader.onload = (event) => {
+          const audio = new Audio(event.target.result);
+          audio.addEventListener('loadedmetadata', () => {
+            // 获取时长并保存
+            if (Math.ceil(audio.duration) > 30) {
+              this.$message.error('音频时长不能超过30秒!');
+              this.voiceLoading = false;
+              return reject(false); // 不允许继续上传
+            }
+            resolve(true); // 允许上传
+          });
+        };
+
+        reader.onerror = () => {
+          this.$message.error('无法读取音频文件!请上传正确的音频文件');
+          reject(false); // 不允许继续上传
+        };
+
+        this.voiceLoading = true;
+        reader.readAsDataURL(file); // 开始读取文件
+      });
+
+    },
+
+    /** 查询sop模板列表 */
+    getList() {
+      this.loading = true;
+      listSopTemp(this.queryParams).then(response => {
+        this.sopTempList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+
+    },
+    // 取消按钮
+    cancel() {
+      this.$store.dispatch("tagsView/delView", this.$route);
+      this.$router.replace('/qw/conversion/sopTemp')
+      // this.reset();
+
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        setting: null,
+        status: "0",
+        sort: null,
+        createTime: null,
+        createBy: null,
+        companyId: null
+      };
+      this.resetForm("form");
+
+    },
+
+
+    /** 修改按钮操作 */
+    handleUpdate(id) {
+      getSopTemp(id).then(response => {
+        this.videoList = []
+        this.form = response.data;
+        this.setting = this.form.list || [];
+
+        if (Array.isArray(this.setting)) {
+          this.setting.forEach(item => {
+            if (item && Array.isArray(item.content)) {
+              item.content.forEach(content => {
+                if (content.isAtAll === undefined || content.isAtAll === null) {
+                  this.$set(content, 'isAtAll', '0') // 默认关闭
+                }
+                // 如果后端返回的是数字,转换为字符串
+                if (typeof content.isAtAll === 'number') {
+                  this.$set(content, 'isAtAll', content.isAtAll.toString())
+                }
+                if (content && Array.isArray(content.setting)) {
+                  content.setting.forEach(setList => {
+                    if (setList && !Object.hasOwn(setList, 'isSalesCallAdded')) {
+                      this.$set(setList, 'isSalesCallAdded', false);
+                    }
+                    if (setList && !Object.hasOwn(setList, 'isUserNameCallAdded')) {
+                      this.$set(setList, 'isUserNameCallAdded', false);
+                    }
+                  });
+                }
+              });
+            }
+          });
+        }
+        this.form.list.forEach(e => e.newDay = e.dayNum)
+        this.dayList = JSON.parse(JSON.stringify(this.form.list));
+        this.videoList.push([]);
+        this.addTag = [];
+        this.setting.forEach(item => {
+          if (item && Array.isArray(item.content)) {
+            item.content.forEach(content => {
+              this.addTag.push({
+                addTag: content.addTag ? JSON.parse(content.addTag) : [],
+                inputVisible: false,
+                inputValue: '',
+                delTag: content.delTag ? JSON.parse(content.delTag) : [],
+                delTagVisible: false,
+                delTagValue: ''
+              });
+            });
+          }
+        });
+        // this.addTag.push({
+        //   addTag: [],
+        //   inputVisible: false,
+        //   inputValue: '',
+        //   delTag: [],
+        //   delTagVisible: false,
+        //   delTagValue: ''
+        // })
+      });
+
+    },
+    /** 提交按钮 */
+    submitForm() {
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除sop模板编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delSopTemp(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有sop模板数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportSopTemp(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+      });
+
+    },
+    updateHtml(val,content) {
+      val || val();
+      if(!!content  && !!content.liveId ){
+        content.liveId = null;
+      }
+    },
+    updateAll(setIndex, list, fieldName, newVal) {
+      if(this.form.sendType == 11 && setIndex == 0) {
+        console.info("更新数据", newVal)
+        for (let index in list.content) {
+          this.$set(list.content[index].setting[0], fieldName, newVal);
+        }
+      }
+    }
+  }
+};
+</script>

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

@@ -844,7 +844,7 @@ export default {
       if (val.appKey == null || val.appKey === '') {
         return this.$message.warning("没有授权码,无法登录企业微信,请授权");
       }
-      loginQwIpad({qwUserId: val.id}).then(res => {
+      loginQwIpad({qwUserId: val.id, isNewVersion:true}).then(res => {
         this.qwUserId = val.id;
         this.qwLogin.code = null;
         this.imageLoading = false;

+ 1 - 0
src/views/taskStatistics/callLog/index.vue

@@ -132,6 +132,7 @@
             <el-table-column label="发送成功数量" align="center" prop="successCount" width="150"/>
             <el-table-column label="发送失败数量" align="center" prop="failCount" width="150"/>
             <el-table-column label="发送中数量" align="center" prop="runningCount" width="150"/>
+            <el-table-column label="接通成功率" align="center" prop="successRate" width="150"/>
             <!--      <el-table-column label="销售名称" align="center" prop="companyUserName" width="180" />-->
             <!--      <el-table-column label="花费金额" align="center" prop="cost" />-->
             <!--      <el-table-column label="手机号" align="center" prop="phone" />-->