yys 2 недель назад
Родитель
Сommit
653d235bd1
49 измененных файлов с 1820 добавлено и 1326 удалено
  1. 0 9
      src/api/company/companyUser.js
  2. 0 8
      src/api/company/companyVoiceRobotic.js
  3. 0 8
      src/api/company/companyWorkflow.js
  4. 0 16
      src/api/company/tagBinding.js
  5. 0 14
      src/api/company/workflowLobster.js
  6. 6 0
      src/api/qw/user.js
  7. 66 0
      src/api/store/userCoupon copy.js
  8. 0 47
      src/permission.js
  9. 86 170
      src/router/index.js
  10. 4 4
      src/store/index.js
  11. 65 2
      src/store/modules/permission.js
  12. 1 0
      src/store/modules/user.js
  13. 14 6
      src/utils/jsencrypt.js
  14. 8 2
      src/utils/liveWS.js
  15. 1 7
      src/utils/request.js
  16. 468 50
      src/views/company/companyConfig/index.vue
  17. 1 1
      src/views/company/companyRecharge/doRecharge.vue
  18. 47 2
      src/views/company/companyUser/index.vue
  19. 1 1
      src/views/company/companyVoiceDialog/index.vue
  20. 5 5
      src/views/company/companyVoiceRobotic/index-old.vue
  21. 10 175
      src/views/company/companyVoiceRobotic/index.vue
  22. 6 61
      src/views/company/companyWorkflow/design.vue
  23. 0 2
      src/views/company/companyWorkflow/index.vue
  24. 3 15
      src/views/company/tag/binding.vue
  25. 0 4
      src/views/company/workflowLobster/WorkflowSolutionEditor.vue
  26. 6 77
      src/views/company/workflowLobster/index.vue
  27. 146 478
      src/views/company/workflowLobster/visual.vue
  28. 3 46
      src/views/company/wxAccount/index.vue
  29. 154 46
      src/views/crm/customer/customerDetail.vue
  30. 149 2
      src/views/crm/customer/index.vue
  31. 16 1
      src/views/fastGpt/fastGptChatSession/index.vue
  32. 8 9
      src/views/index.vue
  33. 9 3
      src/views/live/liveConfig/index.vue
  34. 9 3
      src/views/live/liveConsole/LiveConsole.vue
  35. 19 10
      src/views/login.vue
  36. 20 1
      src/views/member/list.vue
  37. 24 1
      src/views/member/mylist.vue
  38. 130 4
      src/views/qw/externalContact/deptIndex.vue
  39. 149 13
      src/views/qw/externalContact/index.vue
  40. 141 2
      src/views/qw/externalContact/myExternalContact.vue
  41. 11 2
      src/views/qw/externalContactTransfer/index.vue
  42. 21 6
      src/views/qw/friendWelcome/indexNew.vue
  43. 3 3
      src/views/qw/sopTemp/index.vue
  44. 1 1
      src/views/statistics/section/channel.vue
  45. 1 1
      src/views/statistics/section/index.vue
  46. 1 1
      src/views/statistics/section/inline.vue
  47. 1 1
      src/views/statistics/section/today.vue
  48. 3 3
      src/views/user/transfer/index.vue
  49. 3 3
      src/views/users/user/transferLog.vue

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

@@ -368,12 +368,3 @@ export function unBind(userId) {
     data: { userId: userId }
   })
 }
-
-// 查询我的企微列表
-export function myQwList(query) {
-  return request({
-    url: '/company/user/myQwList',
-    method: 'get',
-    params: query
-  })
-}

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

@@ -8,14 +8,6 @@ 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({

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

@@ -9,14 +9,6 @@ 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({

+ 0 - 16
src/api/company/tagBinding.js

@@ -10,13 +10,6 @@ export const tagBindingApi = {
       params
     })
   },
-    getListByStatus: (params) => {
-    return request({
-      url: '/workflow/tag-binding/listByStatus',
-      method: 'get',
-      params
-    })
-  },
 
   // 根据ID获取
   getById: (id) => {
@@ -77,14 +70,5 @@ export const tagBindingApi = {
       method: 'post',
       data: testTags
     })
-  },
-
-  // 批量添加龙虾标签给客户
-  batchBindLobsterTag: (data) => {
-    return request({
-      url: '/workflow/tag-binding/batch-bind-lobster-tag',
-      method: 'post',
-      data
-    })
   }
 }

+ 0 - 14
src/api/company/workflowLobster.js

@@ -21,13 +21,6 @@ export function listWorkflowTemplate(query) {
     params: query
   })
 }
-export function listWorkflowTemplateByStatus(query) {
-  return request({
-    url: '/workflow/template/listTemplate',
-    method: 'get',
-    params: query
-  })
-}
 
 export function getWorkflowTemplateDetail(id) {
   return request({
@@ -44,13 +37,6 @@ export function updateWorkflowTemplate(id, data) {
   })
 }
 
-export function updateWorkflowTemplateStatus(id, status) {
-  return request({
-    url: '/workflow/template/' + id + '/' + status,
-    method: 'put'
-  })
-}
-
 export function saveWorkflowCanvas(id, data) {
   return request({
     url: '/workflow/canvas/' + id,

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

@@ -98,6 +98,12 @@ export function addQwUser(id) {
     method: 'post',
   })
 }
+export function addQwSyncUser(id) {
+  return request({
+    url: '/qw/user/syncUser/' + id,
+    method: 'post',
+  })
+}
 export function addQwUserName(id) {
   return request({
     url: '/qw/user/syncName/' + id,

+ 66 - 0
src/api/store/userCoupon copy.js

@@ -0,0 +1,66 @@
+import request from '@/utils/request'
+
+// 查询会员优惠券列表
+export function listUserCoupon(query) {
+  return request({
+    url: '/store/userCoupon/list',
+    method: 'get',
+    params: query
+  })
+}
+export function getListUserCoupon(query) {
+  return request({
+    url: '/store/userCoupon/getList',
+    method: 'get',
+    params: query
+  })
+}
+// 查询会员优惠券详细
+export function getUserCoupon(id) {
+  return request({
+    url: '/store/userCoupon/' + id,
+    method: 'get'
+  })
+}
+
+// 新增会员优惠券
+export function addUserCoupon(data) {
+  return request({
+    url: '/store/userCoupon',
+    method: 'post',
+    data: data
+  })
+}
+export function sendCoupon(data) {
+  return request({
+    url: '/store/userCoupon/sendCoupon',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改会员优惠券
+export function updateUserCoupon(data) {
+  return request({
+    url: '/store/userCoupon',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除会员优惠券
+export function delUserCoupon(id) {
+  return request({
+    url: '/store/userCoupon/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出会员优惠券
+export function exportUserCoupon(query) {
+  return request({
+    url: '/store/userCoupon/export',
+    method: 'get',
+    params: query
+  })
+}

+ 0 - 47
src/permission.js

@@ -9,55 +9,8 @@ NProgress.configure({ showSpinner: false })
 
 const whiteList = ['/login', '/auth-redirect', '/bind', '/register']
 
-// 管理级页面黑名单:业务客服端不应通过URL直接访问这些管理级路由
-const adminPathBlacklist = [
-  '/company/companyRole',
-  '/company/companyMenu',
-  '/company/companyProfit',
-  '/company/companyRecharge',
-  '/company/companyMoneyLogs',
-  '/company/companyDomainBind',
-  '/company/aiModel',
-  '/saas/',
-  '/system/role',
-  '/system/menu',
-  '/system/dept',
-  '/system/config',
-  '/admin/',
-  '/monitor/',
-  // 龙虾引擎管理级子模块:业务客服不应直接访问
-  '/lobster/model-config',
-  '/lobster/billing',
-  '/lobster/api-registry',
-  '/lobster/dead-letter',
-  '/lobster/event-audit',
-  '/lobster/optimization',
-  '/lobster/prompt',
-  '/lobster/template',
-  '/lobster/sales-corpus',
-  '/lobster/instance'
-]
-
 router.beforeEach((to, from, next) => {
   NProgress.start()
-
-  // 检查管理级页面黑名单:业务客服端禁止直接URL访问
-  if (getToken()) {
-    const fullPath = to.fullPath
-    const isAdminPath = adminPathBlacklist.some(path => fullPath.indexOf(path) !== -1)
-    if (isAdminPath) {
-      // 检查当前用户是否为管理员角色
-      const roles = store.getters.roles
-      const isAdmin = roles.some(role => role === 'admin' || role === 'tenant_admin')
-      if (!isAdmin) {
-        Message.error('您没有权限访问此页面')
-        next({ path: '/401' })
-        NProgress.done()
-        return
-      }
-    }
-  }
-
   if (getToken()) {
     to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
     /* has token*/

+ 86 - 170
src/router/index.js

@@ -61,7 +61,11 @@ export const constantRoutes = [
     component: (resolve) => require(['@/views/error/401'], resolve),
     hidden: true
   },
-  // /wx 空壳路由已移除(个微管理路由在下方正确定义)
+  {
+    path: '/wx',
+    // component: () => import('@/page/help'),
+    hidden: true,
+  },
   {
     path: '/',
     component: Layout,
@@ -108,7 +112,7 @@ export const constantRoutes = [
      hidden: true,
      children: [
        {
-         path: 'updateFastGptRole/:command',
+         path: 'uadateFastGptRole/:command',
          component: (resolve) => require(['@/views/fastGpt/fastGptRole/fastGptRoleUpdate'], resolve),
          name: 'updateAiRole',
          meta: { title: '修改ai角色', activeMenu: '/fastGpt/fastGptRole'}
@@ -137,27 +141,34 @@ export const constantRoutes = [
           component: (resolve) => require(['@/views/qw/sopTemp/addAiChatTemp'], resolve),
           name: 'addAiChatTemp',
           meta: { title: '新增AI对话模板', activeMenu: '/qw/addSopTemp'}
-        },
-        {
-          path: 'updateSopTemp/:id/:type(\\d+)',
-          component: () => import('@/views/qw/sopTemp/updateSopTemp'),
-          name: 'updateSopTemp',
-          meta: { title: '改动SOP模板', activeMenu: '/qw/addSopTemp' }
-        },
-        {
-          path: 'updateTemp/:id/:type(\\d+)',
-          component: () => import('@/views/qw/sopTemp/updateTemp'),
-          name: 'updateTemp',
-          meta: { title: '改动SOP模板', activeMenu: '/qw/updateTemp' }
-        },
-        {
-          path: 'updateAiChatTemp/:id/:type(\\d+)',
-          component: () => import('@/views/qw/sopTemp/updateAiChatTemp'),
-          name: 'updateAiChatTemp',
-          meta: { title: '改动SOP模板', activeMenu: '/qw/addSopTemp' }
         }
       ]
     },
+  {
+    path: '/qw/sopTemp',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: 'updateSopTemp/:id/:type(\\d+)', // 确保 :type 的正则匹配数字
+        component: () => import('@/views/qw/sopTemp/updateSopTemp'),
+        name: 'updateSopTemp',
+        meta: { title: '改动SOP模板', activeMenu: '/qw/addSopTemp' }
+      },
+      {
+        path: 'updateTemp/:id/:type(\\d+)', // 确保 :type 的正则匹配数字
+        component: () => import('@/views/qw/sopTemp/updateTemp'),
+        name: 'updateTemp',
+        meta: { title: '改动SOP模板', activeMenu: '/qw/updateTemp' }
+      },
+	  {
+	    path: 'updateAiChatTemp/:id/:type(\\d+)', // 确保 :type 的正则匹配数字
+	    component: () => import('@/views/qw/sopTemp/updateAiChatTemp'),
+	    name: 'updateAiChatTemp',
+	    meta: { title: '改动SOP模板', activeMenu: '/qw/addSopTemp' }
+	  }
+    ]
+  },
   {
     path: '/qw/sop',
     component: Layout,
@@ -168,24 +179,59 @@ export const constantRoutes = [
         component: (resolve) => require(['@/views/qw/sop/addSop'], resolve),
         name: 'addSop',
         meta: { title: '新增SOP任务', activeMenu: '/qw/sop'}
-      },
+      }
+    ]
+  },
+  {
+    path: '/qw/conversion',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: 'courseFinishTemp/:parentId',
+        component: (resolve) => require(['@/views/course/courseFinishTemp/index'], resolve),
+        name: 'courseFinishTemp',
+        meta: { title: '模板详情', activeMenu: '/qw/conversion'}
+      }
+    ]
+  },
+  {
+    path: '/qw/sop',
+    component: Layout,
+    hidden: true,
+    children: [
       {
         path: 'addAiChatSop/:corpId',
         component: (resolve) => require(['@/views/qw/sop/addAiChatSop'], resolve),
         name: 'addAiChatSop',
-        meta: { title: '新增聊天SOP任务', activeMenu: '/qw/sop'}
-      },
+        meta: { title: '新增>聊天SOP任务', activeMenu: '/qw/sop'}
+      }
+    ]
+  },
+
+  {
+    path: '/qw/sop',
+    component: Layout,
+    hidden: true,
+    children: [
       {
         path: 'updateSop/:id/:type',
         component: (resolve) => require(['@/views/qw/sop/updateSop.vue'], resolve),
         name: 'updateSop',
         meta: { title: '修改/查看SOP任务', activeMenu: '/qw/sop'}
-      },
+      }
+    ]
+  },
+  {
+    path: '/qw/sop',
+    component: Layout,
+    hidden: true,
+    children: [
       {
         path: 'upAiChatDateSop/:id/:type',
         component: (resolve) => require(['@/views/qw/sop/upAiChatDateSop'], resolve),
         name: 'upDateAiChatSop',
-        meta: { title: '修改/查看聊天SOP任务', activeMenu: '/qw/sop'}
+        meta: { title: '修改/查看>聊天SOP任务', activeMenu: '/qw/sop'}
       }
     ]
   },
@@ -234,7 +280,7 @@ export const constantRoutes = [
     {
       path: 'index',
       component: () => import('@/views/company/aiWorkflow/index'),
-      name: 'CompanyAiWorkflow',
+      name: 'AiWorkflow',
       meta: { title: 'AI工作流', activeMenu: '/company/aiWorkflow' }
     }
   ]
@@ -247,15 +293,9 @@ export const constantRoutes = [
     {
       path: '',
       component: () => import('@/views/company/companyWorkflow/index'),
-      name: 'CompanyWorkflowFull',
+      name: 'AiWorkflow',
       meta: { title: 'AI外呼工作流', icon: 'workflow' }
     },
-    {
-      path: 'myList',
-      component: () => import('@/views/company/companyWorkflow/myIndex'),
-      name: 'MyCompanyWorkflow',
-      meta: { title: '我的工作流', activeMenu: '/companyWx/companyWorkflow' }
-    },
     {
       path: 'design',
       component: () => import('@/views/company/companyWorkflow/design'),
@@ -349,143 +389,19 @@ export const constantRoutes = [
             }
         ]
     },
-
-  {
-    path: '/wxSop/sopUserLogsWx',
-    component: Layout,
-    hidden: true,
-    children: [
-      {
-        path: 'sopUserLogsScheduleWx/:id',
-        component: () => import('@/views/wx/sopUserLogsWx/sopUserLogsScheduleWx.vue'),
-        name: 'sopUserLogsScheduleWx',
-        meta: { title: '个微SOP营期', activeMenu: '/wx/wxSop' }
-      },
-      {
-        path: 'detail',
-        component: () => import('@/views/wx/sopUserLogsWx/detail.vue'),
-        name: 'sopUserLogsDetailWx',
-        meta: { title: '个微SOP营期详情', activeMenu: '/wx/wxSop' }
-      }
-    ]
-  },
-
-  {
-    path: '/wxSop/wxSop',
-    component: Layout,
-    hidden: true,
-    children: [
-      {
-        path: 'sopLogsList/:id',
-        component: () => import('@/views/wx/wxSop/sopLogsList.vue'),
-        name: 'WxSopLogsList',
-        meta: { title: '个微SOP执行详情', activeMenu: '/wx/wxSop' }
-      }
-    ]
-  },
-
-  {
-    path: '/companyVoiceRobotic',
-    component: Layout,
-    hidden: true,
-    children: [
-      {
-        path: 'myIndex',
-        component: () => import('@/views/company/companyVoiceRobotic/myIndex.vue'),
-        name: 'MyCompanyVoiceRobotic',
-        meta: { title: '我的音色任务', activeMenu: '/companyWx/companyVoiceRobotic' }
-      }
-    ]
-  },
-
-  // ======== 龙虾引擎 (Lobster Workflow Engine) ========
-  {
-    path: '/lobster',
-    component: Layout,
-    redirect: '/lobster/workflow-generate',
-    name: 'Lobster',
-    meta: { title: '龙虾引擎', icon: 'el-icon-cpu' },
-    children: [
-      {
-        path: 'workflow-generate',
-        component: () => import('@/views/lobster/workflow-generate/index'),
-        name: 'LobsterGenerate',
-        meta: { title: 'AI生成工作流', icon: 'el-icon-magic-stick' }
-      },
-      {
-        path: 'workflow-canvas',
-        component: () => import('@/views/lobster/workflow-canvas/index'),
-        name: 'LobsterCanvas',
-        meta: { title: '工作流画布', icon: 'el-icon-share' }
-      },
-      {
-        path: 'template',
-        component: () => import('@/views/lobster/template/index'),
-        name: 'LobsterTemplate',
-        meta: { title: '工作流模板库', icon: 'el-icon-document' }
-      },
-      {
-        path: 'instance',
-        component: () => import('@/views/lobster/instance/index'),
-        name: 'LobsterInstance',
-        meta: { title: '实例监控', icon: 'el-icon-monitor' }
-      },
-      {
-        path: 'optimization',
-        component: () => import('@/views/lobster/optimization/index'),
-        name: 'LobsterOptimization',
-        meta: { title: 'AI优化建议', icon: 'el-icon-lightbulb' }
-      },
-      {
-        path: 'prompt',
-        component: () => import('@/views/lobster/prompt/index'),
-        name: 'LobsterPrompt',
-        meta: { title: '提示词管理', icon: 'el-icon-edit-outline' }
-      },
-      {
-        path: 'sales-corpus',
-        component: () => import('@/views/lobster/sales-corpus/index'),
-        name: 'SalesCorpus',
-        meta: { title: '销冠语料学习', icon: 'el-icon-star-on' }
-      },
-      {
-        path: 'api-registry',
-        component: () => import('@/views/lobster/api-registry/index'),
-        name: 'LobsterApiRegistry',
-        meta: { title: '接口注册中心', icon: 'el-icon-connection' }
-      },
-      {
-        path: 'dead-letter',
-        component: () => import('@/views/lobster/dead-letter/index'),
-        name: 'LobsterDeadLetter',
-        meta: { title: '死信队列', icon: 'el-icon-warning-outline' }
-      },
-      {
-        path: 'event-audit',
-        component: () => import('@/views/lobster/event-audit/index'),
-        name: 'LobsterEventAudit',
-        meta: { title: '节点审核', icon: 'el-icon-check' }
-      },
-      {
-        path: 'chat-aggregate',
-        component: () => import('@/views/lobster/chat-aggregate/index'),
-        name: 'ChatAggregate',
-        meta: { title: '聚合聊天', icon: 'el-icon-chat-dot-round' }
-      },
-      {
-        path: 'model-config',
-        component: () => import('@/views/lobster/model-config/index'),
-        name: 'LobsterModelConfig',
-        meta: { title: '模型配置', icon: 'el-icon-cpu' }
-      },
-      {
-        path: 'billing',
-        component: () => import('@/views/lobster/billing/index'),
-        name: 'LobsterBilling',
-        meta: { title: 'Token系数管理', icon: 'el-icon-money' }
-      }
-    ]
-  },
+    {
+        path: '/workflow/visual/:id',
+        component: Layout,
+        hidden: true,
+        children: [
+            {
+                path: '',
+                component: () => import('@/views/company/workflowLobster/visual.vue'),
+                name: 'WorkflowLobsterVisual',
+                meta: { title: '工作流可视化编辑', activeMenu: '/company/workflowLobster' }
+            }
+        ]
+    }
 
 ]
 

+ 4 - 4
src/store/index.js

@@ -34,15 +34,15 @@ const store = new Vuex.Store({
     }
   },
   actions: {
-    // 修改 action 以正确传递参数
-    initLiveWs({ commit, state }, { liveWsUrl, liveId, userId }) {
+    // 修改 action 以正确传递参数(含多租户 tenantCode)
+    initLiveWs({ commit, state }, { liveWsUrl, liveId, userId, tenantCode }) {
       // 如果已经存在对应 liveId 的连接,先关闭它
       if (state.liveWs[liveId]) {
         state.liveWs[liveId].close();
       }
 
-      // 创建新的 WebSocket 连接
-      const ws = new LiveWS(liveWsUrl, liveId, userId);
+      // 创建新的 WebSocket 连接(传入 tenantCode 进行多租户隔离)
+      const ws = new LiveWS(liveWsUrl, liveId, userId, tenantCode || undefined);
 
       // 提交到 mutation
       commit('setLiveWs', { ws, liveId });

+ 65 - 2
src/store/modules/permission.js

@@ -40,10 +40,26 @@ const permission = {
       return new Promise(resolve => {
         // 向后端请求路由数据
         getRouters().then(res => {
-          const sdata = JSON.parse(JSON.stringify(res.data))
-          const rdata = JSON.parse(JSON.stringify(res.data))
+          // 过滤不属于销售端的菜单(在后端返回原始数据上过滤,在组件转换之前)
+          const filteredData = filterSaasRoutes(res.data)
+          const sdata = JSON.parse(JSON.stringify(filteredData))
+          const rdata = JSON.parse(JSON.stringify(filteredData))
           const sidebarRoutes = filterAsyncRouter(sdata)
           const rewriteRoutes = filterAsyncRouter(rdata, false, true)
+          // 修复:为顶级路由path添加/前缀
+          rewriteRoutes.forEach(route => {
+            if (route.path && !route.path.startsWith('/')) {
+              route.path = '/' + route.path;
+            }
+          });
+          sidebarRoutes.forEach(route => {
+            if (route.path && !route.path.startsWith('/')) {
+              route.path = '/' + route.path;
+            }
+          });
+          // 修复:消除重复路由name,避免Vue Router重复键警告
+          deduplicateRouteNames(rewriteRoutes)
+          deduplicateRouteNames(sidebarRoutes)
           rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
           commit('SET_ROUTES', rewriteRoutes)
           commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
@@ -56,6 +72,53 @@ const permission = {
   }
 }
 
+// 过滤不属于销售端的菜单
+function filterSaasRoutes(routes) {
+  // 需要过滤掉的组件路径前缀
+  const excludePrefixes = ['admin/', 'system/', 'monitor/']
+  // 需要过滤掉的精确组件路径前缀(company下的通信管理子模块)
+  const excludeExactPrefixes = ['company/companyVoiceBlacklist', 'company/companyVoiceConfig', 'company/companyVoice/', 'company/companyVoicePackage']
+
+  return routes.filter(route => {
+    // 递归过滤子菜单
+    if (route.children && route.children.length > 0) {
+      route.children = filterSaasRoutes(route.children)
+    }
+    // 检查 component 字段是否匹配排除规则
+    const component = route.component
+    if (typeof component === 'string') {
+      if (excludePrefixes.some(prefix => component.startsWith(prefix))) {
+        return false
+      }
+      if (excludeExactPrefixes.some(exact => component.startsWith(exact))) {
+        return false
+      }
+    }
+    // 如果子菜单被全部过滤掉且自身无组件,则移除该空父菜单
+    if (route.children && route.children.length === 0 && !route.component) {
+      return false
+    }
+    return true
+  })
+}
+
+// 递归消除重复路由name,为重复的name追加后缀
+function deduplicateRouteNames(routes, nameSet = new Set()) {
+  routes.forEach(route => {
+    if (route.name) {
+      if (nameSet.has(route.name)) {
+        // 重复name:追加路径后缀使其唯一
+        const suffix = route.path ? '_' + route.path.replace(/[\/\\]/g, '_') : '_' + Date.now()
+        route.name = route.name + suffix
+      }
+      nameSet.add(route.name)
+    }
+    if (route.children && route.children.length) {
+      deduplicateRouteNames(route.children, nameSet)
+    }
+  })
+}
+
 // 遍历后台传来的路由字符串,转换为组件对象
 function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
   return asyncRouterMap.filter(route => {

+ 1 - 0
src/store/modules/user.js

@@ -43,6 +43,7 @@ const user = {
       const tenantCode = userInfo.tenantCode;
       return new Promise((resolve, reject) => {
         checkIsNeedCheck(username, password, code, uuid,tenantCode).then(resp => {
+          console.log("检查是否需要验证", resp)
           if (!resp) {
             // 不需要短信验证,直接登录
             login(username, password, code, uuid,tenantCode).then(res => {

+ 14 - 6
src/utils/jsencrypt.js

@@ -1,11 +1,19 @@
 import JSEncrypt from 'jsencrypt/bin/jsencrypt'
 
-// 注意:前端仅保留公钥用于加密(记住密码功能)
-// 私钥不应暴露在前端代码中,解密应由后端接口完成
+// 密钥对生成 http://web.chacuo.net/netrsakeypair
 
 const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' +
   'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
 
+const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
+  '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
+  'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
+  'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
+  'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
+  'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
+  'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
+  'UP8iWi1Qw0Y='
+
 // 加密
 export function encrypt(txt) {
   const encryptor = new JSEncrypt()
@@ -13,10 +21,10 @@ export function encrypt(txt) {
   return encryptor.encrypt(txt) // 对数据进行加密
 }
 
-// 解密 - 注意:前端不再保留私钥,记住密码功能改为仅保存用户名
-// 如需解密Cookie中的密码,请调用后端解密接口
+// 解密
 export function decrypt(txt) {
-  console.warn('前端解密已禁用,私钥已从前端移除。请使用后端解密接口。')
-  return ''
+  const encryptor = new JSEncrypt()
+  encryptor.setPrivateKey(privateKey) // 设置私钥
+  return encryptor.decrypt(txt) // 对数据进行解密
 }
 

+ 8 - 2
src/utils/liveWS.js

@@ -5,19 +5,25 @@ export class LiveWS {
    * @param {string} url - WebSocket 服务器地址
    * @param {number} liveId - 直播间ID
    * @param {number} userId - 用户ID
+   * @param {string} tenantCode - 租户编码(多租户隔离)
    * @param {number} checkInterval - 检查连接状态的时间间隔,单位毫秒
    * @param {number} reconnectDelay - 连接断开后重连的延迟,单位毫秒
    */
-  constructor(url, liveId, userId, checkInterval = 5000, reconnectDelay = 3000) {
+  constructor(url, liveId, userId, tenantCode, checkInterval = 5000, reconnectDelay = 3000) {
     let timestamp = new Date().getTime()
     let userType = 1
     let signature = CryptoJS.HmacSHA256(
       CryptoJS.enc.Utf8.parse('' + liveId + userId + userType + timestamp),
       CryptoJS.enc.Utf8.parse(timestamp)).toString(CryptoJS.enc.Hex)
-    this.url = url + `?liveId=${liveId}&userId=${userId}&userType=${userType}&timestamp=${timestamp}&signature=${signature}`;
+    let query = `liveId=${liveId}&userId=${userId}&userType=${userType}&timestamp=${timestamp}&signature=${signature}`;
+    if (tenantCode && tenantCode !== 'undefined' && tenantCode !== 'null') {
+      query += `&tenantCode=${tenantCode}`;
+    }
+    this.url = url + '?' + query;
     console.log(this.url)
     this.liveId = liveId;
     this.userId = userId;
+    this.tenantCode = tenantCode;
     this.checkInterval = checkInterval;
     this.reconnectDelay = reconnectDelay;
     this.ws = null;

+ 1 - 7
src/utils/request.js

@@ -2,7 +2,6 @@ import axios from 'axios'
 import { Notification, MessageBox, Message } from 'element-ui'
 import store from '@/store'
 import { getToken } from '@/utils/auth'
-import Cookies from 'js-cookie'
 import errorCode from '@/utils/errorCode'
 
 axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
@@ -22,14 +21,9 @@ service.interceptors.request.use(config => {
 
   // 是否需要设置 token
   const isToken = (config.headers || {}).isToken === false
-  // 始终发送 X-Frontend-Type 和 tenant-code(登录时也需要)
-  config.headers['X-Frontend-Type'] = 'company'
-  const tenantCode = localStorage.getItem('tenantCode') || Cookies.get('tenantCode')
-  if (tenantCode) {
-    config.headers['tenant-code'] = tenantCode
-  }
   if (getToken() && !isToken) {
     config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
+    config.headers['X-Frontend-Type'] = 'company'
   }
   // get请求映射params参数
   if (config.method === 'get' && config.params) {

+ 468 - 50
src/views/company/companyConfig/index.vue

@@ -1,7 +1,59 @@
 <template>
   <div class="app-container">
-      <el-tabs v-model="activeName">
-        <el-tab-pane label="客户管理配置" name="sysConfig">
+      <el-tabs v-model="activeName"    >
+         <el-tab-pane label="CID配置" name="cidConfig">
+          <el-form ref="cidConfigForm" :model="cidConfig" label-width="200px">
+            <el-form-item label="是否开启手机号配置" prop="enablePhoneConfig">
+              <el-switch v-model="cidConfig.enablePhoneConfig"></el-switch>
+            </el-form-item>
+
+            <template v-if="cidConfig.enablePhoneConfig">
+              <el-form-item label="生成条数" prop="generateCount">
+                <el-input-number
+                  v-model="cidConfig.generateCount"
+                  :min="1"
+                  :step="1"
+                  :precision="0"
+                  placeholder="请输入生成条数"
+                ></el-input-number>
+              </el-form-item>
+
+              <el-form-item label="开始位置" prop="startIndex">
+                <el-input-number
+                  v-model="cidConfig.startIndex"
+                  :min="1"
+                  :max="11"
+                  :step="1"
+                  :precision="0"
+                  placeholder="例如: 1"
+                ></el-input-number>
+                <span class="tip-text">(从第几位开始生成)</span>
+              </el-form-item>
+
+              <el-form-item label="结束位置" prop="endIndex">
+                <el-input-number
+                  v-model="cidConfig.endIndex"
+                  :min="1"
+                  :max="11"
+                  :step="1"
+                  :precision="0"
+                  placeholder="例如: 11"
+                ></el-input-number>
+                <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>
+
+            <div class="line"></div>
+            <div style="float:right;margin-right:20px">
+              <el-button type="primary" @click="onSubmitCidConfig">提交</el-button>
+            </div>
+          </el-form>
+        </el-tab-pane>
+          <el-tab-pane label="客户管理配置" name="sysConfig">
           <el-form ref="sysConfig" :model="sysConfig" label-width="120px">
             <el-form-item label="公海回收规则">
                 <el-row>
@@ -17,7 +69,30 @@
             </div>
           </el-form>
         </el-tab-pane>
-        <el-tab-pane label="客户字段配置" name="customerExt">
+        <el-tab-pane label="企微客服配置" name="qwkfConfig">
+          <el-form ref="qwkfConfig" :model="qwkfConfig" label-width="120px">
+            <el-form-item label="企业CoripID">
+              <el-input v-model="qwkfConfig.corpId" style="width:400px"   ></el-input>
+            </el-form-item>
+            <el-form-item label="Secret">
+              <el-input v-model="qwkfConfig.secret" style="width:400px"   ></el-input>
+            </el-form-item>
+            <el-form-item label="Token">
+              <el-input v-model="qwkfConfig.token" style="width:400px"   ></el-input>
+            </el-form-item>
+            <el-form-item label="EncodingAESKey">
+              <el-input v-model="qwkfConfig.encodingAESKey" style="width:400px"   ></el-input>
+            </el-form-item>
+            <el-form-item label="回调地接">
+              <el-input disabled v-model="qwkfConfig.notifyUrl" style="width:600px"   ></el-input>
+            </el-form-item>
+            <div class="line"></div>
+            <div style="float:right;margin-right:20px">
+              <el-button type="primary" @click="onSubmit3">提交</el-button>
+            </div>
+          </el-form>
+        </el-tab-pane>
+        <el-tab-pane  label="客户字段配置" name="customerExt">
           <el-row :gutter="10" class="mb8" style="margin:0px 0px 10px;">
             <el-col :span="1.5">
               <el-button
@@ -59,6 +134,135 @@
               <el-button type="primary" @click="onSubmit2">提交</el-button>
             </div>
         </el-tab-pane>
+<!--        <el-tab-pane label="企微配置" name="qwConfig">-->
+<!--          <el-form ref="qwConfig" :model="qwConfig" label-width="200px">-->
+<!--            <el-form-item label="企业CoripID">-->
+<!--              <el-input v-model="qwConfig.corpId" style="width:400px"   ></el-input>-->
+<!--            </el-form-item>-->
+<!--            <el-form-item label="通讯录Secret">-->
+<!--              <el-input v-model="qwConfig.bookSecret" style="width:400px"   ></el-input>-->
+<!--            </el-form-item>-->
+
+<!--            <el-form-item label="应用Secret">-->
+<!--              <el-input v-model="qwConfig.appSecret" style="width:400px"   ></el-input>-->
+<!--            </el-form-item>-->
+<!--            <el-form-item label="应用AgentId">-->
+<!--              <el-input v-model="qwConfig.AgentId" style="width:400px"   ></el-input>-->
+<!--            </el-form-item>-->
+<!--            <el-form-item label="Token">-->
+<!--              <el-input v-model="qwConfig.token" style="width:400px"   :readonly="true"></el-input>-->
+<!--            </el-form-item>-->
+<!--            <el-form-item label="EncodingAESKey">-->
+<!--              <el-input v-model="qwConfig.encodingAESKey" style="width:400px"   :readonly="true"></el-input>-->
+<!--            </el-form-item>-->
+<!--            <el-form-item label="域名地址">-->
+<!--              <el-input v-model="qwConfig.realmNameURL" style="width:600px"></el-input>-->
+<!--            </el-form-item>-->
+<!--            <el-form-item label="回调地接">-->
+<!--              <el-input v-model="qwConfig.notifyUrl" style="width:600px"   :readonly="true"></el-input>-->
+<!--            </el-form-item>-->
+<!--            <el-form-item label="聊天工具栏跳转地址">-->
+<!--              <el-input v-model="qwConfig.chatToolbar" style="width:600px"   :readonly="true"></el-input>-->
+<!--            </el-form-item>-->
+<!--            <el-form-item label="聊天工具栏实际运用地址">-->
+<!--              <el-input v-model="qwConfig.chatToolbarOAuth2" :rows="2"  type="textarea" style="width:1000px;"   :readonly="true"></el-input>-->
+<!--            </el-form-item>-->
+<!--            <div class="line"></div>-->
+<!--            <div style="float:right;margin-right:20px">-->
+<!--              <el-button type="primary" @click="onSubmit4">提交</el-button>-->
+<!--            </div>-->
+<!--          </el-form>-->
+<!--        </el-tab-pane>-->
+        <el-tab-pane label="AI客服配置" name="AiKfConfig">
+          <el-form ref="AiKfConfig" :model="AiKfConfig" label-width="120px">
+            <el-form-item label="通用Key">
+              <el-input  v-model="AiKfConfig.Key" style="width:600px"   ></el-input>
+            </el-form-item>
+            <el-form-item label="调用地址">
+              <el-input  v-model="AiKfConfig.url" style="width:600px"   ></el-input>
+            </el-form-item>
+            <div class="line"></div>
+            <div style="float:right;margin-right:20px">
+              <el-button type="primary" @click="onSubmit5">提交</el-button>
+            </div>
+          </el-form>
+        </el-tab-pane>
+        <el-tab-pane label="红包商户配置" name="redPacketConfig" >
+          <el-form ref="redPacketConfig" :model="redPacketConfig"  label-width="150px">
+            <el-form-item   label="红包接口类型" prop="isNew">
+              <el-radio-group v-model="redPacketConfig.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="redPacketConfig.appId"  label="请输入appId"></el-input>
+            </el-form-item>
+            <el-form-item   label="小程序appid" prop="appId">
+              <el-input   v-model="redPacketConfig.miniappId"  label="请输入appId"></el-input>
+            </el-form-item>
+            <el-form-item   label="商户号" prop="mchId">
+              <el-input   v-model="redPacketConfig.mchId"  label="请输入mchId"></el-input>
+            </el-form-item>
+            <el-form-item   label="商户密钥" prop="mchKey">
+              <el-input   v-model="redPacketConfig.mchKey"  label="mchKey"></el-input>
+            </el-form-item>
+            <el-form-item   label="p12证书路径" prop="keyPath">
+              <el-input   v-model="redPacketConfig.keyPath"  label="请输入keyPath"></el-input>
+            </el-form-item>
+            <el-form-item   label="apiV3密钥" prop="apiV3Key">
+              <el-input   v-model="redPacketConfig.apiV3Key"  label="请输入apiV3Key"></el-input>
+            </el-form-item>
+            <el-form-item   label="公钥ID" prop="publicKeyId">
+              <el-input   v-model="redPacketConfig.publicKeyId"  label="请输入公钥ID"></el-input>
+            </el-form-item>
+            <el-form-item   label="公钥证书" prop="publicKeyPath">
+              <el-input   v-model="redPacketConfig.publicKeyPath"  label="请输入publicKeyPath"></el-input>
+            </el-form-item>
+            <el-form-item   label="key路径" prop="privateKeyPath">
+              <el-input   v-model="redPacketConfig.privateKeyPath"  label="请输入"></el-input>
+            </el-form-item>
+            <el-form-item   label="cert路径" prop="privateCertPath">
+              <el-input   v-model="redPacketConfig.privateCertPath"  label="请输入"></el-input>
+            </el-form-item>
+            <el-form-item   label="回调地址" prop="notifyUrl">
+              <el-input   v-model="redPacketConfig.notifyUrl"  label="请输入"></el-input>
+            </el-form-item>
+
+            <div style="float:right;margin-right:20px">
+              <el-button type="primary" @click="onSubmit4">提交</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="会员是否小黑屋"><!--会员是否默认黑名单-->
+              <el-row>
+                <el-switch v-model="userIsDefaultBlack"></el-switch>
+              </el-row>
+            </el-form-item>
+            <div style="float:right;margin-right:20px">
+              <el-button type="primary" @click="onSubmit6">提交</el-button>
+            </div>
+          </el-form>
+        </el-tab-pane>
+        <el-tab-pane label="配置销售端隐藏总账号" name="adminIsShowForm">
+          <el-form ref="adminIsShowForm" label-width="140px">
+            <el-form-item label="账号是否显示">
+              <el-row>
+                <el-switch v-model="adminIsShow"></el-switch>
+                <span style="margin-left: 10px;">
+                  <el-tag :type="adminIsShow ? 'success' : 'info'" size="mini">
+                    {{ adminIsShow ? '当前显示' : '当前隐藏' }}
+                  </el-tag>
+                </span>
+              </el-row>
+            </el-form-item>
+            <div style="float:right;margin-right:20px">
+              <el-button type="primary" @click="onSubmit7">提交</el-button>
+            </div>
+          </el-form>
+        </el-tab-pane>
       </el-tabs>
 
       <el-dialog :title="customerExt.title" :visible.sync="customerExt.open" width="500px" append-to-body>
@@ -85,59 +289,130 @@
 </template>
 
 <script>
-import { getConfigKey, updateConfig } from "@/api/company/companyConfig";
+import { getCompanyInfo } from "@/api/company/company";
+import { getConfigKey,updateConfig,configUserCheck } from "@/api/company/companyConfig";
 import { listCustomerExt, getCustomerExt, delCustomerExt, addCustomerExt, updateCustomerExt } from "@/api/crm/customerExt";
 
 export default {
   name: "config",
+  watch: {
+    qwConfig: {
+      handler(newValue) {
+        // 根据新值更新
+        this.qwConfig.chatToolbarOAuth2="https://open.weixin.qq.com/connect/oauth2/authorize?" +
+          "appid="+newValue.corpId+"" +
+          "&redirect_uri="+newValue.chatToolbar+"" +
+          "&response_type=code" +
+          "&scope=snsapi_base" +
+          "&state=STATE" +
+          "&agentid="+newValue.AgentId+"#wechat_redirect";
+
+        this.qwConfig.notifyUrl=newValue.realmNameURL+"/qw/data/"+this.company.companyId;
+      },
+      deep: true
+    }
+  },
   data() {
     return {
-      statusOptions: [],
-      customerExt: {
-        open: false,
-        title: "扩展字段"
+      cidConfig: {
+        enablePhoneConfig: false,
+        generateCount: 0,
+        startIndex: 1,
+        endIndex: 5
       },
-      customerExtForm: {
-        name: "",
-        status: "1",
-        sort: "1",
+      adminIsShow: false,
+      company:null,
+      statusOptions:[],
+      customerExt:{
+        open:false,
+        title:"扩展字段"
+      },
+      customerExtForm:{
+        name:"",
+        status:"1",
+        sort:"1",
+      },
+      customerExtRules:{
+
       },
-      customerExtRules: {},
       customerExtList: [],
       activeName: 'sysConfig',
-      sysConfig: {
-        tel: "18900000000",
-        noticeType: 1,
-        visitLimt: 0,
-        contractLimt: 0
+      customerConfig:{
+          rlLimit: 1,
+          txLimit: 1,
+          hsLimit: 1
       },
-      sysConfigForm: {},
-      customerConfig: {
-        rlLimit: 1,
-        txLimit: 1,
-        hsLimit: 1
+      sysConfig:{
+          tel:"18900000000",
+          noticeType:1,
+          visitLimt:0,
+          contractLimt:0
       },
-      customerConfigForm: {}
+      sysConfigForm:{},
+      customerConfigForm:{},
+      qwkfConfig:{
+
+      },
+      AiKfConfig:{},
+      AiKfConfigForm:{},
+
+      qwConfig:{
+      },
+      qwConfigForm:{},
+      qwkfConfigForm:{},
+      companyUserConfig: {},
+      adminIsShowForm:{},
+      userIsDefaultBlack: null,
+      redPacketConfig:{},
+
+      redPacketConfigForm:{},
+      cidConfigForm:{}
     };
   },
   created() {
+    this.getCompanyInfo();
     this.getConfigKey("sys:config");
+    this.getConfigKey("qw:config");
+    this.getConfigKey("sys:qw:config");
     this.getConfigKey("customer:config");
+    this.getConfigKey("sys:AiKf:config");
+    this.getConfigKey("company:admin:show");
+    this.getConfigKey("redPacket:config");
+    this.getConfigKey("cId.config");
     this.getDicts("sys_company_status").then((response) => {
       this.statusOptions = response.data;
     });
     this.getCustomerExt()
   },
+  mounted() {
+
+  },
+
   methods: {
+    getCompanyInfo(){
+      getCompanyInfo().then(response => {
+        this.company = response.data;
+        if(response.data.fsUserIsDefaultBlack != null){
+          const userIsDefaultBlack = response.data.fsUserIsDefaultBlack
+          if(userIsDefaultBlack == 1){
+            this.userIsDefaultBlack = true
+          } else{
+            this.userIsDefaultBlack = false
+          }
+        }
+      });
+    },
+    /** 新增按钮操作 */
     handleCustomerExtAdd() {
       this.customerExt.open = true;
       this.customerExt.title = "添加客户字段";
-      this.customerExtForm = {
-        name: "",
-        status: "1",
-        sort: "1",
+      this.customerExtForm={
+        name:"",
+        status:"1",
+        sort:"1",
       }
     },
+    /** 修改按钮操作 */
     handleCustomerExtUpdate(row) {
       const extId = row.extId
       getCustomerExt(extId).then(response => {
@@ -147,6 +422,7 @@ export default {
         this.customerExt.title = "修改客户字段";
       });
     },
+    /** 提交按钮 */
     submitCustomerExtForm() {
       this.$refs["customerExtForm"].validate(valid => {
         if (valid) {
@@ -155,6 +431,7 @@ export default {
               if (response.code === 200) {
                 this.getCustomerExt("修改成功");
                 this.customerExt.open = false;
+                this.getList();
               }
             });
           } else {
@@ -169,6 +446,7 @@ export default {
         }
       });
     },
+    /** 删除按钮操作 */
     handleCustomerExtDelete(row) {
       const extIds = row.extId || this.ids;
       this.$confirm('是否确认删除客户字段扩展编号为"' + extIds + '"的数据项?', "警告", {
@@ -183,28 +461,79 @@ export default {
         }).catch(function() {});
     },
     getCustomerExt() {
-      var data = {}
+      var data={}
       listCustomerExt(data).then(response => {
         this.customerExtList = response.data;
       });
     },
-    getConfigKey(key) {
-      getConfigKey(key).then((response) => {
-        if (key == "sys:config") {
-          this.sysConfigForm = response.data;
-          if (response.data.configValue != null) {
-            this.sysConfig = JSON.parse(response.data.configValue);
-          }
-        } else if (key == "customer:config") {
-          this.customerConfigForm = response.data;
-          if (response.data.configValue != null) {
-            this.customerConfig = JSON.parse(response.data.configValue);
-          }
-        }
-      });
+    getConfigKey(key){
+        getConfigKey(key).then((response) => {
+            if(key=="sys:config"){
+                this.sysConfigForm=response.data;
+                if(response.data.configValue!=null){
+                    this.sysConfig=JSON.parse(response.data.configValue);
+                }
+            }
+            else if(key=="qw:config"){
+                this.qwkfConfigForm=response.data;
+                if(response.data.configValue!=null){
+                    this.qwkfConfig=JSON.parse(response.data.configValue);
+                    this.qwkfConfig.notifyUrl="http://kf.qw.ifeiyu100.com/app/common/weixinkfAuth/"+this.company.companyId;
+                }
+            }
+            else if(key=="customer:config"){
+                this.customerConfigForm=response.data;
+                if(response.data.configValue!=null){
+                    this.customerConfig=JSON.parse(response.data.configValue);
+                }
+            }
+            else if(key=="sys:qw:config"){
+                this.qwConfigForm=response.data;
+                if(response.data.configValue!=null){
+                    this.qwConfig=JSON.parse(response.data.configValue);
+
+                }
+                this.qwConfig.token="1o62d3YxvdHd4LEUiltnu7sK";
+                this.qwConfig.encodingAESKey="UJfTQ5qKTKlegjkXtp1YuzJzxeHlUKvq5GyFbERN1iU";
+                this.qwConfig.notifyUrl=this.qwConfig.realmNameURL+"/qw/data/"+this.company.companyId;
+                this.qwConfig.chatToolbar="https://company.his.cdwjyyh.com/qwh5/?companyId="+this.company.companyId;
+                this.qwConfig.chatToolbarOAuth2="https://open.weixin.qq.com/connect/oauth2/authorize?" +
+                  "appid="+this.qwConfig.corpId+"" +
+                  "&redirect_uri="+this.qwConfig.chatToolbar+"" +
+                  "&response_type=code" +
+                  "&scope=snsapi_base" +
+                  "&state=STATE" +
+                  "&agentid="+this.qwConfig.AgentId+"#wechat_redirect";
+            }else if (key=="sys:AiKf:config"){
+              this.AiKfConfigForm=response.data;
+              if(response.data.configValue!=null){
+                this.AiKfConfig=JSON.parse(response.data.configValue);
+              }
+            }else if (key=="companyUser:config"){
+              this.companyUserConfig=response.data;
+              if(response.data.configValue != null){
+                this.userIsDefaultBlack = JSON.parse(response.data.configValue);
+              }
+            }else if(key == "company:admin:show"){
+              this.adminIsShowForm = response.data;
+              if(response.data.configValue != null){
+                this.adminIsShow = JSON.parse(response.data.configValue);
+              }
+            }else if(key=="redPacket:config"){
+              this.redPacketConfigForm=response.data;
+              if(response.data.configValue!=null){
+                this.redPacketConfig=JSON.parse(response.data.configValue);
+              }
+            }else if(key == "cId.config"){
+              this.cidConfigForm = response.data;
+              if(response.data.configValue != null){
+                this.cidConfig = JSON.parse(response.data.configValue);
+              }
+            }
+        });
     },
     onSubmit1() {
-      this.sysConfigForm.configValue = JSON.stringify(this.sysConfig);
+      this.sysConfigForm.configValue=JSON.stringify(this.sysConfig);
       updateConfig(this.sysConfigForm).then(response => {
         if (response.code === 200) {
           this.msgSuccess("修改成功");
@@ -213,7 +542,7 @@ export default {
       });
     },
     onSubmit2() {
-      this.customerConfigForm.configValue = JSON.stringify(this.customerConfig);
+      this.customerConfigForm.configValue=JSON.stringify(this.customerConfig);
       updateConfig(this.customerConfigForm).then(response => {
         if (response.code === 200) {
           this.msgSuccess("修改成功");
@@ -221,17 +550,105 @@ export default {
         }
       });
     },
+    onSubmit3() {
+      this.qwkfConfigForm.configValue=JSON.stringify(this.qwkfConfig);
+      updateConfig(this.qwkfConfigForm).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("修改成功");
+          this.getConfigKey("sys:config");
+        }
+      });
+    },
+    // onSubmit4() {
+    //   this.qwConfigForm.configValue=JSON.stringify(this.qwConfig);
+    //   updateConfig(this.qwConfigForm).then(response => {
+    //     if (response.code === 200) {
+    //       this.msgSuccess("修改成功");
+    //       this.getConfigKey("sys:qw:config");
+    //     }
+    //   });
+    // },
+    onSubmit4() {
+      this.redPacketConfigForm.configValue=JSON.stringify(this.redPacketConfig);
+      updateConfig(this.redPacketConfigForm).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("修改成功");
+          this.getConfigKey("redPacket:config");
+        }
+      });
+    },
+    onSubmit5() {
+      this.AiKfConfigForm.configValue=JSON.stringify(this.AiKfConfig);
+      updateConfig(this.AiKfConfigForm).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("修改成功");
+          this.getConfigKey("sys:AiKf:config");
+        }
+      });
+    },
+    onSubmit6() {
+      this.companyUserConfig.configValue=JSON.stringify(this.userIsDefaultBlack);
+      configUserCheck({userIsDefaultBlack: this.userIsDefaultBlack}).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("修改成功");
+          this.getConfigKey("companyUser:config");
+        }
+      });
+    },
+    onSubmit7() {
+      // 实现提交逻辑
+      this.adminIsShowForm.configValue=JSON.stringify(this.adminIsShow);
+      updateConfig(this.adminIsShowForm).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("修改成功");
+          this.getConfigKey("company:admin:show");
+        }
+      })
+    },
+    onSubmitCidConfig() {
+      if (this.cidConfig.enablePhoneConfig) {
+        if (!this.cidConfig.generateCount || this.cidConfig.generateCount < 1) {
+          this.msgError('生成条数不能为空且不能小于1');
+          return;
+        }
+        if (!this.cidConfig.startIndex || this.cidConfig.startIndex < 1 || this.cidConfig.startIndex > 11) {
+          this.msgError('开始位置不能为空,且必须在1到11之间');
+          return;
+        }
+        if (!this.cidConfig.endIndex || this.cidConfig.endIndex < 1 || this.cidConfig.endIndex > 11) {
+          this.msgError('结束位置不能为空,且必须在1到11之间');
+          return;
+        }
+        if (this.cidConfig.startIndex > this.cidConfig.endIndex) {
+          this.msgError('开始位置不能大于结束位置');
+          return;
+        }
+
+        if (this.cidConfig.endIndex - this.cidConfig.startIndex < 4) {
+          this.msgError('开始和结束位置之间的位数不能少于4位');
+          return;
+        }
+      }
+
+      this.cidConfigForm.configValue = JSON.stringify(this.cidConfig);
+      updateConfig(this.cidConfigForm).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("修改成功");
+          this.getConfigKey("cId.config");
+        }
+      });
+    },
   }
 };
 </script>
 <style scoped lang="scss">
-.tip {
-  color: indianred;
+.tip{
+  color:indianred;
 }
-.line {
+.line{
   margin: 10px 0;
-  background-color: gainsboro;
-  height: 1px;
+  background-color:gainsboro;
+  height:1px;
 }
 .el-row {
   margin-bottom: 0px;
@@ -240,3 +657,4 @@ export default {
   }
 }
 </style>
+

+ 1 - 1
src/views/company/companyRecharge/doRecharge.vue

@@ -49,7 +49,7 @@
 <script>
 import { listCompanyRecharge, getCompanyRecharge, delCompanyRecharge, addCompanyRecharge, updateCompanyRecharge, exportCompanyRecharge } from "@/api/company/companyRecharge";
 import { getCompanyInfo } from "@/api/company/company";
-import { getConfigByKey } from '@/api/company/companyConfig'
+import { getConfigByKey } from '@/api//company/companyConfig'
 import {queryOrder, weixinPay, wxQrPay} from "@/api/company/pay";
 import QRCode from 'qrcodejs2'
 

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

@@ -67,6 +67,16 @@
              v-hasPermi="['qw:user:sync']"
            >同步企微员工和部门</el-button>
           </el-col>
+
+          <el-col :span="1.5">
+            <el-button
+              type="primary"
+              plain
+              size="mini"
+              @click="synOpenUser=true"
+              v-hasPermi="['qw:user:sync']"
+            >同步企微员工正确账户</el-button>
+          </el-col>
           <el-col :span="1.5">
             <el-button
               type="primary"
@@ -348,6 +358,25 @@
         <el-button @click="synOpen=false">取 消</el-button>
       </div>
     </el-dialog>
+    <el-dialog title="选择企微主体" :visible.sync="synOpenUser" width="800px" append-to-body>
+      <el-form   label-width="80px">
+        <el-form-item label="企微公司" prop="corpId">
+          <el-select v-model="synform.corpId" placeholder="企微公司"  >
+            <el-option
+              v-for="dict in myQwCompanyList"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="synUserSubmitForm">确 定</el-button>
+        <el-button @click="synOpenUser=false">取 消</el-button>
+      </div>
+    </el-dialog>
+
 
     <el-dialog title="选择企微主体" :visible.sync="synNameOpen" width="800px" append-to-body>
       <el-form   label-width="80px">
@@ -703,7 +732,7 @@
 
 
     <ai-sip-call-user ref="aiSipCallUser" v-show="false" @refreshParentData="getList" />
-    
+
   </div>
 </template>
 
@@ -733,7 +762,7 @@ import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
 import {bindQwUser, getQwUserList, addQwUser, getQwUser, getQwUserByIds,addQwUserName} from '@/api/qw/user';
 import { syncDept } from '@/api/qw/qwDept';
-import { getMyQwUserList,getMyQwCompanyList } from "@/api/qw/user";
+import {getMyQwUserList, getMyQwCompanyList, addQwSyncUser} from "@/api/qw/user";
 import  selectUser  from "@/views/company/components/selectQwUser.vue";
 import { getConfigByKey } from "@/api/company/companyConfig";
 import axios from "axios";
@@ -774,6 +803,7 @@ export default {
       isNeedRegisterMember: [],
       synform:{corpId:null},
       synOpen:false,
+      synOpenUser:false,
       synNameform:{corpId:null},
       synNameOpen:false,
       // 非单个禁用
@@ -1397,7 +1427,22 @@ export default {
         //loadingRock.close();
       });
     },
+    synUserSubmitForm() {
+      this.synOpen = false;
+      this.loading = true;
 
+
+      addQwSyncUser(this.synform.corpId).then(response => {
+        //this.msgSuccess("同步成功");
+        this.msgSuccess("正在同步中...");
+        this.getList();
+        this.synOpenUser = false;
+      }).finally(() => {
+        this.loading = false;
+        this.synOpenUser = false;
+        //loadingRock.close();
+      });
+    },
     synNameSubmitForm() {
       this.synNameOpen = false;
       this.loading = true;

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

@@ -78,7 +78,7 @@
             size="mini"
             type="text"
             @click="initUrl(scope.row.id)"
-            v-hasPermi="['company:companyVoiceDialog:list']"
+            v-hasPermi="['system:companyVoiceDialog:list']"
           >配置话术</el-button>
           <el-button
             size="mini"

+ 5 - 5
src/views/company/companyVoiceRobotic/index-old.vue

@@ -75,7 +75,7 @@
           icon="el-icon-refresh"
           size="mini"
           @click="updateStatusFun"
-          v-hasPermi="['company:companyVoiceRobotic:list']"
+          v-hasPermi="['system:companyVoiceRobotic:list']"
         >更新状态
         </el-button>
       </el-col>
@@ -140,14 +140,14 @@
             size="mini"
             type="text"
             @click="calleesOpen(scope.row.id)"
-            v-hasPermi="['company:companyVoiceRobotic:list']"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
           >客户列表
           </el-button>
           <el-button
             size="mini"
             type="text"
             @click="wxOpen(scope.row.id)"
-            v-hasPermi="['company:companyVoiceRobotic:list']"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
           >加微统计
           </el-button>
           <el-button
@@ -155,7 +155,7 @@
             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="['company:companyVoiceRobotic:list']"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
           >启动任务
           </el-button>
           <el-button
@@ -163,7 +163,7 @@
             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="['company:companyVoiceRobotic:list']"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
           >停止任务
           </el-button>
           <el-button

+ 10 - 175
src/views/company/companyVoiceRobotic/index.vue

@@ -64,7 +64,7 @@
 <!--          icon="el-icon-refresh"-->
 <!--          size="mini"-->
 <!--          @click="updateStatusFun"-->
-<!--          v-hasPermi="['company:companyVoiceRobotic:list']"-->
+<!--          v-hasPermi="['system:companyVoiceRobotic:list']"-->
 <!--        >更新状态-->
 <!--        </el-button>-->
 <!--      </el-col>-->
@@ -87,7 +87,7 @@
       </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 == 1">个微</el-tag> -->
           <el-tag v-if="scope.row.isWeCom == 2">企微</el-tag>
         </template>
       </el-table-column>
@@ -139,20 +139,20 @@
             size="mini"
             type="text"
             @click="calleesOpen(scope.row.id)"
-            v-hasPermi="['company:companyVoiceRobotic:list']"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
           >客户列表</el-button>
           <el-button
             size="mini"
             type="text"
             @click="wxOpen(scope.row.id,scope.row.isWeCom)"
-            v-hasPermi="['company:companyVoiceRobotic:list']"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
           >加微管理</el-button>
           <el-button
             size="mini"
             type="text"
             icon="el-icon-document"
             @click="showExecLogs(scope.row)"
-            v-hasPermi="['company:companyVoiceRobotic:list']"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
           >执行日志</el-button>
           <el-button
             size="mini"
@@ -165,14 +165,14 @@
             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="['company:companyVoiceRobotic:list']"
+            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="['company:companyVoiceRobotic:list']"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
           >停止任务</el-button>
           <el-button
             size="mini"
@@ -381,10 +381,10 @@
             </div>
             <el-form-item label="添加类型" prop="isWeCom">
               <el-radio-group v-model="form.isWeCom">
-                <el-radio :label="1" border>
+                <!-- <el-radio :label="1" border>
                   <i class="el-icon-pie-chart"></i>
                   个微
-                </el-radio>
+                </el-radio> -->
                 <el-radio :label="2" border>
                   <i class="el-icon-star-on"></i>
                   企微
@@ -474,49 +474,6 @@
             <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" 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="操作" align="center" class-name="small-padding fixed-width">
           <template slot-scope="scope">
             <el-button
@@ -895,11 +852,10 @@ 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: {AiTagPanel, draggable, customerDetails, customerSelect, qwUserSelect,qwUserSelectTwo,CallCenterPhoneBar},
+  components: { draggable, customerDetails, customerSelect, qwUserSelect,qwUserSelectTwo,CallCenterPhoneBar},
   data() {
     return {
       submitFormLoading:false,
@@ -1153,17 +1109,6 @@ export default {
     }
   },
   methods: {
-
-      getIntentionText(intention) {
-          const intentionMap = {
-              high: "高意向",
-              medium: "中意向",
-              low: "低意向",
-              none: "无意向"
-          };
-          return intentionMap[intention] || intention;
-      },
-
     getSmsTempDropList(){
       getSmsTempList().then(res=>{
         this.smsTempList = res.data;
@@ -2200,114 +2145,4 @@ 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>

+ 6 - 61
src/views/company/companyWorkflow/design.vue

@@ -282,7 +282,7 @@
               </el-form-item>
             </div>
              <!-- AI加微配置 -->
-            <div v-if="selectedNode.nodeType == 'AI_ADD_WX_TASK_NEW'" class="property-section">
+            <div v-if="selectedNode.nodeType == 'AI_ADD_WX_TASK'" class="property-section">
               <div class="section-title">
                 <i class="el-icon-chat-dot-round"></i>加微配置
               </div>
@@ -401,28 +401,13 @@
                   />
                 </el-select>
               </el-form-item>
-              <el-form-item label="转人工业务组">
-                  <el-select
-                      v-model="selectedNode.nodeConfig.busiGroupId"
-                      filterable
-                      placeholder="请选择技能组"
-                      @change="handleConfigChange"
-                  >
-                      <el-option
-                          v-for="item in easyCallBusiGroupList"
-                          :key="item.groupId"
-                          :label="item.bizGroupName"
-                          :value="item.groupId"
-                      />
-                  </el-select>
-              </el-form-item>
               <el-form-item label="外呼线路">
                 <el-select
                   :disabled="editAiDisable"
                   v-model="selectedNode.nodeConfig.gatewayId"
                   filterable
                   placeholder="请选择外呼线路"
-                  @change="handleConfigChange('handleGateway')"
+                  @change="handleConfigChange"
                 >
                   <el-option
                     v-for="item in easyCallGatewayList"
@@ -448,22 +433,6 @@
                   />
                 </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"
@@ -552,7 +521,7 @@
             </div>
 
             <!-- 条件判断 -->
-            <div v-if="edgeSourceNode.nodeType == 'AI_CALL_TASK' || edgeSourceNode.nodeType == 'AI_SEND_MSG_TASK' || edgeSourceNode.nodeType == 'AI_ADD_WX_TASK_NEW' || edgeSourceNode.nodeType == 'AI_QW_ADD_WX_TASK'"  class="property-section">
+            <div v-if="edgeSourceNode.nodeType == 'AI_CALL_TASK' || edgeSourceNode.nodeType == 'AI_SEND_MSG_TASK' || edgeSourceNode.nodeType == 'AI_ADD_WX_TASK' || edgeSourceNode.nodeType == 'AI_QW_ADD_WX_TASK'"  class="property-section">
               <div class="section-title">
                 <i class="el-icon-set-up"></i>条件判断
                 <el-button type="success" size="mini" icon="el-icon-plus" @click="addCondition" class="add-condition-btn">新增条件</el-button>
@@ -591,7 +560,7 @@
               </div>
 
               <!-- 加微信任务条件 -->
-              <div v-if="edgeSourceNode.nodeType == 'AI_ADD_WX_TASK_NEW' || edgeSourceNode.nodeType == 'AI_QW_ADD_WX_TASK'" class="conditions-container">
+              <div v-if="edgeSourceNode.nodeType == 'AI_ADD_WX_TASK' || edgeSourceNode.nodeType == 'AI_QW_ADD_WX_TASK'" class="conditions-container">
                 <div v-for="(item, index) in selectedEdge.conditionExprObj" :key="index" class="condition-item">
                   <div class="condition-header">
                     <span class="condition-number">条件 {{ index + 1 }}</span>
@@ -663,7 +632,7 @@ import {
 import { getWorkflow, addWorkflow, updateWorkflow, getNodeTypes, getWorkflowVersionDetail } from '@/api/company/companyWorkflow'
 import {getDicts} from "@/api/system/dict/data";
 import { getGatewayList, getLlmAccountList, getVoiceCodeList, getBusiGroupList } from '@/api/company/easyCall'
-import { listAll } from '@/api/company/wxDialog';
+// import { listAll } from '@/api/company/wxDialog';
 export default {
   name: 'WorkflowDesign',
   data() {
@@ -759,13 +728,7 @@ export default {
       easyCallGatewayList: [],     // 外呼线路(网关)列表
       easyCallLlmAccountList: [],  // 大模型底座列表
       easyCallVoiceCodeList: [],   // 音色列表
-      allEasyCallVoiceCodeList: [],   // 接口原始数据
       easyCallBusiGroupList: [],   // tts厂商(技能组)列表
-      voiceSourceOptions: [
-        { label: '阿里云TTS', value: 'aliyun_tts' },
-        { label: '豆包语音', value: 'doubao_vcl_tts' },
-        { label: '电信语音', value: 'chinatelecom_tts' }
-      ],
     }
   },
   created() {
@@ -788,11 +751,6 @@ export default {
     //   console.log("------")
     //   console.log(this.wxDialogList)
     // })
-    listAll().then(e => {
-      this.wxDialogList = e.data;
-      console.log("------")
-      console.log(this.wxDialogList)
-    })
 
     this.getDicts("sys_qw_qw_wx_add_way").then(response => {
       this.qwWxAddWayOptions = response.data;
@@ -829,18 +787,9 @@ 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;
-       this.selectedNode.nodeConfig.ttsModels =  voice[0].ttsModels;
       }
       else if( !!v && v === "handleGateway" ){
        let gateway =  this.easyCallGatewayList.filter(e=>e.id == this.selectedNode.nodeConfig.gatewayId);
@@ -894,9 +843,6 @@ export default {
         if (!this.selectedNode.nodeConfig.recallTimes && this.selectedNode.nodeConfig.recallTimes !== 0) {
           this.$set(this.selectedNode.nodeConfig, 'recallTimes', 0)
         }
-          if (this.selectedNode.nodeConfig.busiGroupId === undefined) {
-              this.$set(this.selectedNode.nodeConfig, 'busiGroupId', null)
-          }
         // 初始化外呼模式,默认为人工外呼(1)
         if (!this.selectedNode.nodeConfig.callMode) {
           this.$set(this.selectedNode.nodeConfig, 'callMode', 1)
@@ -921,7 +867,6 @@ export default {
           this.easyCallLlmAccountList = res.data || []
         })
         getVoiceCodeList().then(res => {
-          this.allEasyCallVoiceCodeList = res.data || []
           this.easyCallVoiceCodeList = res.data || []
         })
         getBusiGroupList().then(res => {
@@ -1046,7 +991,7 @@ export default {
         end: '#ff4d4f',
         condition: '#faad14',
         AI_CALL_TASK: '#1890ff',
-        AI_ADD_WX_TASK_NEW: '#722ed1',
+        AI_ADD_WX_TASK: '#722ed1',
         AI_SEND_MSG_TASK: '#eb2f96',
         DELAY_TASK: '#13c2c2'
       }

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

@@ -89,8 +89,6 @@
           />
         </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">

+ 3 - 15
src/views/company/tag/binding.vue

@@ -122,7 +122,6 @@
 
 <script>
 import { tagBindingApi } from '@/api/company/tagBinding'
-import { listWorkflowTemplateByStatus } from '@/api/company/workflowLobster'
 
 export default {
   name: 'TagBinding',
@@ -143,9 +142,7 @@ export default {
         tagName: '',
         templateId: null,
         priority: 0,
-        matchCondition: '',
-        templateName: ''
-
+        matchCondition: ''
       },
       rules: {
         tagCode: [{ required: true, message: '请输入标签编码', trigger: 'blur' }],
@@ -177,13 +174,8 @@ export default {
       }
     },
     async loadTemplateOptions() {
-      try {
-        const res = await listWorkflowTemplateByStatus({ status: 1 })
-        this.templateOptions = res.data || []
-      } catch (error) {
-        this.templateOptions = []
-        this.$message.error('获取模板列表失败')
-      }
+      // TODO: 从工作流模板API加载模板列表
+      this.templateOptions = []
     },
     handleSearch() {
       this.getList()
@@ -202,7 +194,6 @@ export default {
         tagCode: '',
         tagName: '',
         templateId: null,
-        templateName: '',
         priority: 0,
         matchCondition: ''
       }
@@ -218,7 +209,6 @@ export default {
         tagCode: row.tagCode,
         tagName: row.tagName,
         templateId: row.templateId,
-        templateName: row.templateName || '',
         priority: row.priority,
         matchCondition: row.matchCondition
       }
@@ -227,8 +217,6 @@ export default {
     async handleSubmit() {
       try {
         await this.$refs.formRef.validate()
-        const selectedTemplate = this.templateOptions.find(item => item.id === this.form.templateId)
-        this.form.templateName = selectedTemplate ? selectedTemplate.templateName : ''
         if (this.form.id) {
           await tagBindingApi.update(this.form.id, this.form)
           this.$message.success('修改成功')

+ 0 - 4
src/views/company/workflowLobster/WorkflowSolutionEditor.vue

@@ -96,9 +96,6 @@
             <el-form-item label="消息模板" v-if="node.nodeType === 2">
               <el-input v-model="node.messageTemplate" :size="fieldSize" type="textarea" :rows="compact ? 2 : 3" :disabled="readonly" />
             </el-form-item>
-            <el-form-item label="发送时间" v-if="node.nodeType === 2">
-              <el-input v-model="node.sendTime" :size="fieldSize" :disabled="readonly" placeholder="例如:09:00" />
-            </el-form-item>
             <el-form-item label="条件表达式" v-if="node.nodeType === 3">
               <el-input v-model="node.conditionExpr" :size="fieldSize" type="textarea" :rows="compact ? 2 : 3" :disabled="readonly" />
             </el-form-item>
@@ -182,7 +179,6 @@ export default {
         nodeType: 2,
         nodeConfig: '{}',
         messageTemplate: '',
-        sendTime: '',
         conditionExpr: '',
         nextNodeCode: ''
       })

+ 6 - 77
src/views/company/workflowLobster/index.vue

@@ -56,41 +56,14 @@
         <el-table-column prop="templateName" label="模板名称" min-width="180" />
         <el-table-column prop="templateCode" label="模板编码" min-width="140" />
         <el-table-column prop="industryType" label="行业类型" width="110" />
-        <el-table-column prop="status" label="状态" width="90">
-          <template slot-scope="scope">
-            <el-tag size="mini" :type="scope.row.status === 1 ? 'success' : 'info'">
-              {{ scope.row.status === 1 ? '已发布' : '未发布' }}
-            </el-tag>
-          </template>
-        </el-table-column>
+        <el-table-column prop="status" label="状态" width="80" />
         <el-table-column prop="createTime" label="创建时间" width="160" />
         <el-table-column label="操作" width="220" fixed="right">
           <template slot-scope="scope">
-            <div class="table-action-group">
-              <el-button class="action-btn" type="text" @click="handlePreview(scope.row)">预览</el-button>
-              <el-button v-if="scope.row.status !== 1" class="action-btn" type="text" @click="handleEditTemplate(scope.row)">编辑</el-button>
-              <el-button class="action-btn" type="text" @click="handleVisual(scope.row)">流程图</el-button>
-              <el-button
-                v-if="scope.row.status !== 1"
-                class="action-btn danger-btn"
-                type="text"
-                @click="handleDeleteTemplate(scope.row)"
-              >删除</el-button>
-              <el-button
-                v-if="scope.row.status !== 1"
-                class="action-btn"
-                type="text"
-                :loading="statusChangingId === scope.row.id"
-                @click="handlePublish(scope.row)"
-              >发布</el-button>
-              <el-button
-                v-else
-                class="action-btn"
-                type="text"
-                :loading="statusChangingId === scope.row.id"
-                @click="handleUnpublish(scope.row)"
-              >取消发布</el-button>
-            </div>
+            <el-button type="text" @click="handlePreview(scope.row)">预览</el-button>
+            <el-button type="text" @click="handleEditTemplate(scope.row)">编辑</el-button>
+            <el-button type="text" @click="handleVisual(scope.row)">流程图</el-button>
+            <el-button type="text" style="color:#f56c6c" @click="handleDeleteTemplate(scope.row)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -129,7 +102,6 @@ import {
   listWorkflowTemplate,
   getWorkflowTemplateDetail,
   updateWorkflowTemplate,
-  updateWorkflowTemplateStatus,
   deleteWorkflowTemplate,
   aiGenerateWorkflow,
   getGenerateResultDetail,
@@ -153,7 +125,6 @@ export default {
       templateDialogVisible: false,
       templateDialogMode: 'preview',
       templateSaving: false,
-      statusChangingId: null,
       page: {
         current: 1,
         size: 10,
@@ -322,9 +293,7 @@ export default {
       await this.openTemplateDialog(row, 'edit')
     },
     handleVisual(row) {
-      this.$router.push({
-        path: '/workflow/visual/' + row.id,
-      })
+      this.$router.push('/workflow/visual/' + row.id)
     },
     async openTemplateDialog(row, mode) {
       this.templateDialogMode = mode
@@ -383,28 +352,6 @@ export default {
           this.$message.error(e.message || '删除失败')
         }
       }
-    },
-    async handlePublish(row) {
-      await this.changeTemplateStatus(row, 1)
-    },
-    async handleUnpublish(row) {
-      await this.changeTemplateStatus(row, 0)
-    },
-    async changeTemplateStatus(row, status) {
-      const actionText = status === 1 ? '发布' : '取消发布'
-      try {
-        await this.$confirm(`确认${actionText}该模板吗?`, '提示', { type: 'warning' })
-        this.statusChangingId = row.id
-        await updateWorkflowTemplateStatus(row.id, status)
-        this.$message.success(`${actionText}成功`)
-        await this.loadTemplateList()
-      } catch (e) {
-        if (e !== 'cancel') {
-          this.$message.error(e.message || `${actionText}失败`)
-        }
-      } finally {
-        this.statusChangingId = null
-      }
     }
   }
 }
@@ -450,23 +397,5 @@ export default {
     display: flex;
     justify-content: flex-end;
   }
-
-  .table-action-group {
-    display: flex;
-    flex-wrap: wrap;
-    align-items: center;
-    gap: 2px 10px;
-  }
-
-  ::v-deep .table-action-group .action-btn {
-    margin: 0;
-    min-width: 44px;
-    text-align: center;
-    padding: 2px 0;
-  }
-
-  ::v-deep .table-action-group .danger-btn {
-    color: #f56c6c;
-  }
 }
 </style>

Разница между файлами не показана из-за своего большого размера
+ 146 - 478
src/views/company/workflowLobster/visual.vue


+ 3 - 46
src/views/company/wxAccount/index.vue

@@ -89,7 +89,6 @@
       <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="wxRemark" />
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
           <el-button
@@ -106,13 +105,6 @@
             @click="handleDelete(scope.row)"
             v-hasPermi="['company:companyWx:remove']"
           >删除</el-button>
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-refresh"
-            @click="handleSyncCustomer(scope.row)"
-            v-hasPermi="['company:companyWx:edit']"
-          >同步客户</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -126,8 +118,8 @@
     />
 
     <!-- 添加或修改个微账号对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="微信昵称" prop="wxNickName">
           <el-input v-model="form.wxNickName" placeholder="请输入微信昵称" />
         </el-form-item>
@@ -142,9 +134,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="请输入6位微信备注前缀(数字/字母/中文)" />
-        </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -155,7 +144,7 @@
 </template>
 
 <script>
-import { listCompanyAccount, getCompanyAccount, delCompanyAccount, addCompanyAccount, updateCompanyAccount, exportCompanyAccount, companyListAll, syncWx } from "@/api/company/companyAccount";
+import { listCompanyAccount, getCompanyAccount, delCompanyAccount, addCompanyAccount, updateCompanyAccount, exportCompanyAccount, companyListAll } from "@/api/company/companyAccount";
 import {getAllUserlist} from "@/api/company/companyUser";
 
 
@@ -195,24 +184,6 @@ export default {
       form: {},
       // 表单校验
       rules: {
-        wxNickName: [
-          { required: true, message: '请输入微信昵称', trigger: 'blur' }
-        ],
-        wxNo: [
-          { required: true, message: '请输入微信号', trigger: 'blur' }
-        ],
-        phone: [
-          { required: true, message: '请输入手机号', trigger: 'blur' },
-          { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
-        ],
-        companyUserId: [
-          { required: true, message: '请选择员工', trigger: 'change' }
-        ],
-        wxRemark: [
-          { required: true, message: '请输入微信备注前缀', trigger: 'blur' },
-          { len: 6, message: '微信备注前缀必须为6位', trigger: 'blur' },
-          { pattern: /^[0-9a-zA-Z\u4e00-\u9fa5]{6}$/, message: '微信备注前缀只能输入6位数字、字母或中文', trigger: 'blur' }
-        ]
       }
     };
   },
@@ -332,20 +303,6 @@ export default {
           this.download(response.msg);
         }).catch(function() {});
     },
-    /** 同步客户按钮操作 */
-    handleSyncCustomer(row) {
-      this.$confirm('是否确认同步该个微账号的客户数据?', "提示", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(() => {
-          return syncWx({ accountId: row.id });
-        }).then(response => {
-          if (response.code === 200) {
-            this.msgSuccess("同步客户成功");
-          }
-        }).catch(function() {});
-    },
   }
 };
 </script>

+ 154 - 46
src/views/crm/customer/customerDetail.vue

@@ -98,14 +98,13 @@
                     </div>
                 </div>
                 <!-- AI 沟通总结 -->
-                <div class="card card-highlight">
+                <div class="card">
                     <div class="card-header">
                         <h3><i class="fas fa-robot"></i> AI 沟通总结</h3>
                     </div>
-                    <div class="summary-content">
-                        <p class="summary-text">{{ getCommunicationSummary() }}</p>
+                    <div class="summary-text compact">
+                        {{ getCommunicationSummary() }}
                     </div>
-                    <div class="update-time-corner">沟通时间:{{ getUpdateTime() }}</div>
                 </div>
                 <!-- 沟通记录 -->
                 <div class="card card-table">
@@ -136,7 +135,7 @@
                                             {{ getIntentionDegreeFromRecord(record) }}
                                         </span>
                                 </td>
-                                <td class="record-cell">{{ record.createTime }}</td>
+                                <td class="record-cell">{{ parseTime(record.createTime, '{y}-{m}-{d} {h}:{i}:{s}') || '-' }}</td>
                                 <td class="record-cell">
                                     <button @click="viewChat(record)" class="btn-view-chat">
                                         <i class="fas fa-comments"></i> 聊天详情
@@ -260,7 +259,7 @@
                     </div>
                     <div class="intention-section">
                         <div class="intention-header">
-                            <span class="intention-label">客户意向度</span>
+                            <span class="intention-label"><i class="fas fa-heart"></i> 客户意向度</span>
                             <el-tooltip placement="top" effect="light">
                                 <i class="el-icon-info intention-info-icon"></i>
                                 <div slot="content" class="intention-tooltip">
@@ -879,29 +878,35 @@ export default {
     background: linear-gradient(90deg, #10b981 0%, #34d399 100%);
 }
 
+/* 徽章改为“标签”风格(类似 AI 标签) */
 .risk-unknown .risk-badge {
-    background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%);
-    box-shadow: 0 2px 8px rgba(107, 114, 128, 0.3);
+    background: #f8fafc;
+    border-color: #e5eaf1;
+    color: #64748b;
 }
 
 .risk-none .risk-badge {
-    background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
-    box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
+    background: #f0fdf4;
+    border-color: #bbf7d0;
+    color: #16a34a;
 }
 
 .risk-low .risk-badge {
-    background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
-    box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
+    background: #eff6ff;
+    border-color: #bfdbfe;
+    color: #2563eb;
 }
 
 .risk-medium .risk-badge {
-    background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
-    box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);
+    background: #fffbeb;
+    border-color: #fde68a;
+    color: #d97706;
 }
 
 .risk-high .risk-badge {
-    background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
-    box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
+    background: #fef2f2;
+    border-color: #fecaca;
+    color: #dc2626;
 }
 
 .risk-card:hover {
@@ -914,38 +919,26 @@ export default {
     display: inline-flex;
     align-items: center;
     gap: 6px;
-    padding: 6px 14px;
-    border-radius: 8px;
-    font-size: 16px;
-    font-weight: 700;
-    color: white;
-    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
-    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
+    padding: 5px 10px;
+    border-radius: 10px;
+    font-size: 13px;
+    font-weight: 600;
+    border: 1px solid #e5eaf1;
+    box-shadow: none;
+    transition: all 0.2s ease;
 }
 
 .risk-badge::before {
     content: '';
-    width: 6px;
-    height: 6px;
-    background: white;
-    border-radius: 50%;
-    animation: pulse 2s infinite;
-}
-
-@keyframes pulse {
-    0%, 100% {
-        opacity: 1;
-        transform: scale(1);
-    }
-    50% {
-        opacity: 0.5;
-        transform: scale(1.2);
-    }
+    width: 8px;
+    height: 8px;
+    border-radius: 3px;
+    background: currentColor;
+    opacity: 0.35;
 }
 
 .risk-card:hover .risk-badge {
-    transform: scale(1.05);
+    transform: translateY(-1px);
 }
 
 /* 风险分析内容 */
@@ -1864,7 +1857,7 @@ export default {
     color: #64748b;
     display: flex;
     align-items: center;
-    gap: 4px;
+    gap: 6px;
 }
 
 .intention-label::before {
@@ -1875,6 +1868,19 @@ export default {
     border-radius: 2px;
 }
 
+.intention-label i {
+    width: 20px;
+    height: 20px;
+    border-radius: 6px;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 12px;
+    color: #fff;
+    background: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%);
+    box-shadow: 0 2px 6px rgba(245, 158, 11, 0.35);
+}
+
 /* 水印风格意向度显示 - 按等级着色 */
 .intention-watermark {
     font-size: 59px;
@@ -2034,11 +2040,17 @@ export default {
 }
 
 .card-header h3 i {
-    font-size: 20px;
+    width: 24px;
+    height: 24px;
+    border-radius: 7px;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 13px;
+    color: #fff;
     background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-    -webkit-background-clip: text;
-    -webkit-text-fill-color: transparent;
-    background-clip: text;
+    box-shadow: 0 3px 8px rgba(102, 126, 234, 0.35);
+    -webkit-text-fill-color: #fff;
 }
 
 .card-highlight .card-header h3 i {
@@ -2047,5 +2059,101 @@ export default {
     -webkit-text-fill-color: white;
 }
 
+/* 模仿参考图的企业微信分析台风格(覆盖) */
+.customer-container {
+    max-width: 100%;
+    padding: 12px;
+    background: #f4f6fa;
+}
+
+.main-grid-three-columns {
+    grid-template-columns: 300px minmax(640px, 1fr) 320px;
+    gap: 12px;
+    align-items: start;
+}
+
+.card {
+    border-radius: 10px;
+    border: 1px solid #e6ebf2;
+    box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
+    margin-bottom: 10px;
+    padding: 12px;
+    background: #fff;
+}
+
+.card::before {
+    display: none;
+}
+
+.card:hover {
+    transform: none;
+    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
+    border-color: #dbe4f0;
+}
+
+.card-header {
+    border-bottom: 1px solid #edf1f7;
+    margin-bottom: 10px;
+    padding-bottom: 8px;
+}
+
+.card-header h3 {
+    font-size: 16px;
+    font-weight: 600;
+    color: #1f2937;
+}
+
+.card-header h3 i {
+    width: 18px;
+    height: 18px;
+    border-radius: 4px;
+    font-size: 10px;
+    background: #4f7cff;
+    box-shadow: none;
+}
+
+.summary-text.compact {
+    font-size: 14px;
+    line-height: 1.75;
+    color: #334155;
+    max-height: 132px;
+}
+
+.risk-card,
+.card-focus {
+    background: #fff;
+    border: 1px solid #e6ebf2;
+}
+
+.risk-analysis {
+    background: #f8fafc;
+    border: 1px solid #edf2f7;
+}
+
+.tag-item,
+.focus-item,
+.profile-item {
+    background: #f8fafc;
+    border-color: #e5eaf1;
+}
+
+.intention-label i {
+    width: 18px;
+    height: 18px;
+    border-radius: 4px;
+    font-size: 10px;
+    background: #4f7cff;
+    box-shadow: none;
+}
+
+.records-table th {
+    background: #f8fafc;
+    color: #475569;
+}
+
+.records-table tbody tr:hover {
+    background: #f8fbff;
+}
+
 
 </style>

+ 149 - 2
src/views/crm/customer/index.vue

@@ -129,6 +129,29 @@
               <el-option key="0"  label="否" value="0" />
             </el-select>
           </el-form-item>
+          <el-form-item label="流失风险" prop="attritionLevel">
+            <el-select style="width:220px" v-model="queryParams.attritionLevel" placeholder="请选择流失风险等级" clearable size="small">
+                <el-option label="全部" value="" />
+                <el-option label="未知" :value="0" />
+                <el-option label="无风险" :value="1" />
+                <el-option label="低风险" :value="2" />
+                <el-option label="中风险" :value="3" />
+                <el-option label="高风险" :value="4" />
+            </el-select>
+      </el-form-item>
+      <el-form-item label="意向度">
+          <div >
+                <el-select style="width:220px" v-model="queryParams.intentionDegree" placeholder="请选择意向度" clearable size="small">
+                  <el-option label="A" value="A" />
+                  <el-option label="B" value="B" />
+                  <el-option label="C" value="C" />
+                  <el-option label="D" value="D" />
+                  <el-option label="E" value="E" />
+                  <el-option label="F" value="F" />
+                  
+                </el-select>
+            </div>
+      </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>
@@ -234,7 +257,100 @@
                 <el-tag prop="status" v-for="(item, index) in typeOptions"    v-if="scope.row.customerType==item.dictValue">{{item.dictLabel}}</el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="标签" align="center" prop="tags" />
+          <el-table-column label="标签" align="center" prop="properties" width="200">
+            <template slot-scope="scope">
+              <div v-if="scope.row.properties && scope.row.properties.length" style="text-align: left;">
+                <el-tooltip
+                  v-for="(item, index) in scope.row.properties.slice(0, 3)"
+                  :key="index"
+                  placement="top"
+                  effect="light"
+                >
+                  <div slot="content" style="max-width: 420px; word-break: break-word;">
+                    {{ item.propertyName }}:{{ item.propertyValue }}
+                  </div>
+                  <el-tag style="margin: 0 6px 6px 0; max-width: 100%;">
+                    {{ shortenText(item.propertyName + ':' + item.propertyValue, 16) }}
+                  </el-tag>
+                </el-tooltip>
+                <el-tooltip
+                  v-if="scope.row.properties.length > 3"
+                  placement="top"
+                  effect="light"
+                >
+                  <div slot="content" style="max-width: 360px;">
+                    <div
+                      v-for="(item, idx) in scope.row.properties.slice(3)"
+                      :key="'more-' + idx"
+                      style="margin-bottom: 4px;"
+                    >
+                      {{ item.propertyName }}:{{ item.propertyValue }}
+                    </div>
+                  </div>
+                  <el-tag type="info" style="margin: 0 6px 6px 0;">
+                    +{{ scope.row.properties.length - 3 }}
+                  </el-tag>
+                </el-tooltip>
+              </div>
+              <span v-else>-</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="流失风险" align="center" prop="attritionLevel">
+            <template slot-scope="scope">
+              <el-tag v-if="scope.row.attritionLevel == null" type="info">未分析</el-tag>
+              <el-tag v-if="scope.row.attritionLevel === 0" type="info">未知</el-tag>
+              <el-tag v-else-if="scope.row.attritionLevel === 1" type="success">无风险</el-tag>
+              <el-tag v-else-if="scope.row.attritionLevel === 2" type="info">低风险</el-tag>
+              <el-tag v-else-if="scope.row.attritionLevel === 3" type="warning">中风险</el-tag>
+              <el-tag v-else-if="scope.row.attritionLevel === 4" type="danger">高风险</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="意向度" align="center" prop="intentionDegree">
+            <template slot-scope="scope">
+              <el-tag v-if="scope.row.intentionDegree !== null && scope.row.intentionDegree !== undefined && scope.row.intentionDegree !== ''">
+                {{ scope.row.intentionDegree }}
+              </el-tag>
+              <span v-else>-</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="关注点" align="center" prop="customerFocusJson" width="220">
+            <template slot-scope="scope">
+              <div v-if="parseFocusPoints(scope.row.customerFocusJson).length" style="text-align: left;">
+                <el-tooltip
+                  v-for="(item, index) in parseFocusPoints(scope.row.customerFocusJson).slice(0, 2)"
+                  :key="index"
+                  placement="top"
+                  effect="light"
+                >
+                  <div slot="content" style="max-width: 420px; word-break: break-word;">
+                    {{ item }}
+                  </div>
+                  <el-tag style="margin: 0 6px 6px 0; max-width: 100%; background: #fff; border: 1px solid #dcdfe6; color: #606266;">
+                    {{ shortenText(item, 14) }}
+                  </el-tag>
+                </el-tooltip>
+                <el-tooltip
+                  v-if="parseFocusPoints(scope.row.customerFocusJson).length > 2"
+                  placement="top"
+                  effect="light"
+                >
+                  <div slot="content" style="max-width: 420px;">
+                    <div
+                      v-for="(item, idx) in parseFocusPoints(scope.row.customerFocusJson).slice(2)"
+                      :key="'focus-more-' + idx"
+                      style="margin-bottom: 4px;"
+                    >
+                      {{ item }}
+                    </div>
+                  </div>
+                  <el-tag style="margin: 0 6px 6px 0; background: #fff; border: 1px solid #dcdfe6; color: #606266;">
+                    +{{ parseFocusPoints(scope.row.customerFocusJson).length - 2 }}
+                  </el-tag>
+                </el-tooltip>
+              </div>
+              <span v-else>-</span>
+            </template>
+          </el-table-column>
           <el-table-column label="备注" align="center" prop="remark" />
           <el-table-column label="进线客户详情" align="center" :show-overflow-tooltip="true" prop="registerDesc" />
           <el-table-column label="领取时间" align="center" prop="receiveTime" />
@@ -262,6 +378,13 @@
                 @click="handleShow(scope.row)"
                 v-hasPermi="['crm:customer:query']"
               >查看</el-button>
+              <el-button
+                v-if="scope.row.attritionLevel !== null && scope.row.attritionLevel !== undefined"
+                size="mini"
+                type="text"
+                @click="openAiDrawer(scope.row)"
+                v-hasPermi="['crm:analyze:list']"
+              >AI 分析</el-button>
               <el-button
                 v-if="scope.row.isReceive==1"
                 size="mini"
@@ -292,6 +415,18 @@
       >
         <customer-details  ref="customerDetails" />
     </el-drawer>
+    <el-drawer
+    size="75%"
+    :title="aiAnalyze.title"
+    :visible.sync="aiAnalyze.open"
+    append-to-body
+  >
+    <customer-detail
+      ref="customerAiDetail"
+      :customer-id="aiAnalyze.customerId"
+      :customer-row="aiAnalyze.customerRow"
+    />
+  </el-drawer>
 
 
 
@@ -349,6 +484,7 @@ import editSource from '../components/editSource.vue';
 import customerSource from '../components/customerSource.vue';
 import customerAssignList from '../components/customerAssignList.vue';
 import assignUser from '../components/assignUser.vue';
+import customerDetail from './customerDetail.vue';
 export default {
   name: "Customer",
   components: {assignUser,customerAssignList,addBatchSms,editSource, customerDetails,Treeselect,customerSource },
@@ -487,7 +623,13 @@ export default {
         source: [
           { required: true, message: "客户来源不能为空", trigger: "blur" }
         ],
-      }
+      },
+      aiAnalyze: {
+        title: "AI 分析",
+        open: false,
+        customerId: null,
+        customerRow: null,
+      },
     };
   },
   watch: {
@@ -531,6 +673,11 @@ export default {
     this.getList();
   },
   methods: {
+    openAiDrawer(row) {
+      this.aiAnalyze.customerId = row.customerId;
+      this.aiAnalyze.customerRow = row;
+      this.aiAnalyze.open = true;
+    },
     handleShow(row){
       this.show.open=true;
       var that=this;

+ 16 - 1
src/views/fastGpt/fastGptChatSession/index.vue

@@ -149,7 +149,7 @@
       </el-table-column>
       <el-table-column label="角色昵称" align="center" prop="roleName" />
 	  <el-table-column label="企微账号" align="center" prop="qwUserName" />
-	  
+
       <el-table-column label="接待时间" align="center" prop="createTime" >
         <template slot-scope="scope">
           <span v-if="!scope.row.updateTime && scope.row.status === 1">
@@ -396,12 +396,27 @@ export default {
     },
     /** 搜索按钮操作 */
     handleQuery() {
+      // 处理时间参数
+      if (this.dateRange && this.dateRange.length === 2) {
+        this.queryParams.beginTime = this.dateRange[0];
+        this.queryParams.endTime = this.dateRange[1];
+      } else {
+        this.queryParams.beginTime = null;
+        this.queryParams.endTime = null;
+      }
       this.queryParams.pageNum = 1;
       this.getList();
     },
     /** 重置按钮操作 */
     resetQuery() {
+      // 清空时间选择器
+      this.dateRange = [];
+      // 清空时间查询参数
+      this.queryParams.beginTime = null;
+      this.queryParams.endTime = null;
+      // 重置表单
       this.resetForm("queryForm");
+      // 执行查询
       this.handleQuery();
     },
     // 多选框选中数据

+ 8 - 9
src/views/index.vue

@@ -111,7 +111,7 @@
         </el-col>
       </el-row>
 
-      <el-row :gutter="20">
+      <le-row :gutter="20">
         <el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16">
           <div class="operatetitle">
             经营数据
@@ -163,7 +163,7 @@
                   <count-to :start-val="0" :end-val="versionLimit" :duration="3600" class="card-panel-num" />
                 </span>
               </div>
-              <el-progress :percentage="versionLimit ? Math.min(100, Math.round(todayWatchUserCount/versionLimit*100)) : 0" :show-text="false"
+              <el-progress :percentage="todayWatchUserCount/versionLimit" :show-text="false"
                            color="#409EFF"></el-progress>
             </div>
             <div class="operatetitle-card">
@@ -227,7 +227,7 @@
             </div>
           </div>
         </el-col>
-      </el-row>
+      </le-row>
     </el-card>
     <!-- 分析概览 (Analysis Overview) -->
     <div class="analysis-section" shadow="never">
@@ -1173,14 +1173,10 @@ export default {
       redPacketInfo().then(res=>{
         if(res.code === 200){
           if(res.data){
-            this.redBalance = res.data.redBalance || 0;
-            this.redTodayComsumption = res.data.redTodayComsumption || 0;
+            this.redBalance = res.data.redBalance;
+            this.redTodayComsumption = res.data.redTodayComsumption;
           }
         }
-      }).catch(() => {
-        // 红包接口不可用时静默处理
-        this.redBalance = 0;
-        this.redTodayComsumption = 0;
       })
 
       trafficLog().then(res=>{
@@ -1468,6 +1464,7 @@ export default {
 
       getWatchCourseStatisticsData({ ...param }).then(res => {
         if (res.code === 200) {
+          console.log(res.data);
           // 根据实际数据结构调整
           let data = res.data;
           let watchUserCountList = data.map(e => e.watchCount);     // 观看次数
@@ -1486,8 +1483,10 @@ export default {
 
     },
     initDealerChartNew() {
+      console.log("初始化 initDealerChartNew");
       this.dealerChartNew = echarts.init(this.$refs.dealerChartNew)
       this.dealerChartNew.setOption(dealerOptionNew)
+      console.log("初始化结束 initDealerChartNew");
     },
     handleThisMonthOrderCount(){
       thisMonthOrderCount().then(res=>{

+ 9 - 3
src/views/live/liveConfig/index.vue

@@ -72,6 +72,7 @@ import LiveRedConf from './liveRedConf.vue'
 import LiveLotteryConf from './liveLotteryConf.vue'
 import LiveReplay from './liveReplay.vue'
 import Preview from './preview.vue'
+import Cookies from 'js-cookie'
 import LiveCoupon from './liveCoupon.vue'
 import Barrage from './barrage.vue'
 import { listLive, getLive, delLive, addLive, updateLive, exportLive,selectCompanyTalent,handleShelfOrUn,handleDeleteSelected } from "@/api/live/live";
@@ -129,11 +130,16 @@ export default {
   },
   methods: {
     connectWebSocket() {
-      this.$store.dispatch('initLiveWs', {
+      const params = {
         liveWsUrl: this.liveWsUrl,
         liveId: this.liveId,
-        userId: this.userId
-      })
+        userId: this.userId,
+      };
+      const tenantCode = Cookies.get('tenantCode');
+      if (tenantCode) {
+        params.tenantCode = tenantCode;
+      }
+      this.$store.dispatch('initLiveWs', params)
       this.socket = this.$store.state.liveWs[this.liveId]
       this.socket.onmessage = (event) => this.handleWsMessage(event)
     },

+ 9 - 3
src/views/live/liveConsole/LiveConsole.vue

@@ -267,6 +267,7 @@ import { listLiveSingleMsg,delLiveMsg } from '@/api/live/liveMsg'
 import { getLive } from '@/api/live/live'
 import { consoleList } from '@/api/live/task'
 import ScreenScale from './ScreenScale.vue'; // 路径根据实际位置调整
+import Cookies from 'js-cookie'
 
 
 export default {
@@ -832,11 +833,16 @@ export default {
       })
     },
     connectWebSocket() {
-      this.$store.dispatch('initLiveWs', {
+      const params = {
         liveWsUrl: this.liveWsUrl,
         liveId: this.liveId,
-        userId: this.userId
-      })
+        userId: this.userId,
+      };
+      const tenantCode = Cookies.get('tenantCode');
+      if (tenantCode) {
+        params.tenantCode = tenantCode;
+      }
+      this.$store.dispatch('initLiveWs', params)
       this.socket = this.$store.state.liveWs[this.liveId]
       this.socket.onmessage = (event) => this.handleWsMessage(event)
     },

+ 19 - 10
src/views/login.vue

@@ -40,7 +40,7 @@
             <img slot="suffix" src="../assets/images/eyeopen.png" class="input-icon2" v-else @click.stop="changetype()"/>
           </el-input>
         </el-form-item>
-        <el-form-item prop="code" v-if="captchaOnOff">
+        <el-form-item prop="code">
           <el-input
             v-model="loginForm.code"
             auto-complete="off"
@@ -125,15 +125,18 @@ export default {
         ],
         password: [
           { required: true, trigger: "blur", message: "密码不能为空" },
-          { min: 5, max: 20, message: "密码长度为5-20位", trigger: ["blur", "change"] }
+          {
+            pattern: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,20}$/,
+            message: "密码长度为8-20 位,必须包含字母、数字和特殊字符",
+            trigger: ["blur", "change"],
+          }
         ],
         code: [{ required: true, trigger: "change", message: "验证码不能为空" }]
       },
       loading: false,
       redirect: undefined,
       passwordtype:'password',
-      ispassword:true,
-      captchaOnOff: true
+      ispassword:true
     };
   },
   watch: {
@@ -194,19 +197,17 @@ export default {
     },
     getCode() {
       getCodeImg().then(res => {
-        this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;
-        if (this.captchaOnOff) {
-          this.codeUrl = "data:image/gif;base64," + res.img;
-          this.loginForm.uuid = res.uuid;
-        }
+        this.codeUrl = "data:image/gif;base64," + res.img;
+        this.loginForm.uuid = res.uuid;
       });
     },
     getCookie() {
       const username = Cookies.get("username");
+      const password = Cookies.get("password");
       const rememberMe = Cookies.get('rememberMe')
       this.loginForm = {
         username: username === undefined ? this.loginForm.username : username,
-        password: '',
+        password: password === undefined ? this.loginForm.password : decrypt(password),
         rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
       };
     },
@@ -214,6 +215,7 @@ export default {
     // 微信扫码成功回调
     handleWechatLoginSuccess(token) {
       this.loading = false
+      console.log("父组件收到 loginSuccess:", token);
       this.$store.commit("SET_TOKEN", token);
       setToken(token);
       // 登录成功后检查是否是首次登录
@@ -221,22 +223,27 @@ export default {
     },
 
     handleLogin() {
+      console.log("handleLogin")
       this.$refs.loginForm.validate(valid => {
         if (valid) {
           this.loading = true;
           if (this.loginForm.rememberMe) {
             Cookies.set("username", this.loginForm.username, { expires: 30 });
+            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
             Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
             Cookies.set('tenantCode', this.loginForm.tenantCode, { expires: 30 });
           } else {
             Cookies.remove("username");
+            Cookies.remove("password");
             Cookies.remove('rememberMe');
             Cookies.remove('tenantCode');
           }
           this.$store
             .dispatch("Login", this.loginForm)
             .then(res => {
+              console.log("login:" + res);
               if (res.needSms) {
+                console.log("打开弹窗")
                 this.wechatDialogVisible = true;
                 // 等 visible 更新后,直接调用弹窗 open()
                 this.$nextTick(() => {
@@ -252,6 +259,8 @@ export default {
               this.reloadRuntimeConfig()
             })
             .catch(res => {
+              console.log("登录失败")
+              console.log(res)
               this.loading = false;
               this.getCode();
             });

+ 20 - 1
src/views/member/list.vue

@@ -545,8 +545,27 @@ export default {
 
     /** 重置按钮操作 */
     resetQuery() {
+      // 1. 清空日期
       this.dateRange = [];
-      this.resetForm("queryForm");
+
+      // 2. 清空表单绑定的 queryParams(真正生效的重置)
+      this.queryParams = {
+        pageNum: 1,
+        pageSize: 10,
+        nickname: null,
+        isMyFsUser: false,
+        phone: null,
+        tagIds: [],
+        tabValue: "0",
+        watchCourseType: "0",
+        missCourseStatus: "0",
+        continueMissCourseSort: "0",
+        registerStartTime: null,
+        registerEndTime: null,
+        projectId: null,
+      };
+
+      // 3. 重新查询
       this.handleQuery();
     },
 

+ 24 - 1
src/views/member/mylist.vue

@@ -575,8 +575,31 @@ export default {
 
     /** 重置按钮操作 */
     resetQuery() {
+      // 1. 清空日期范围
       this.dateRange = [];
-      this.resetForm("queryForm");
+
+      // 2. 清空表单输入框(真正生效)
+      if (this.$refs.queryForm) {
+        this.$refs.queryForm.resetFields();
+      }
+
+      // 3. 强制还原查询参数(保险)
+      this.queryParams = {
+        pageNum: 1,
+        pageSize: 10,
+        nickname: null,
+        phone: null,
+        tagIds: [],
+        tabValue: "0",
+        watchCourseType: "0",
+        missCourseStatus: "0",
+        continueMissCourseSort: "0",
+        registerStartTime: null,
+        registerEndTime: null,
+        projectId: null,
+      };
+
+      // 4. 重新查询
       this.handleQuery();
     },
 

+ 130 - 4
src/views/qw/externalContact/deptIndex.vue

@@ -365,7 +365,62 @@
           </div>
         </template>
       </el-table-column>
-
+      <el-table-column label="流失风险" align="center" prop="attritionLevel">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.attritionLevel == null" type="info">未分析</el-tag>
+          <el-tag v-if="scope.row.attritionLevel === 0" type="info">未知</el-tag>
+          <el-tag v-else-if="scope.row.attritionLevel === 1" type="success">无风险</el-tag>
+          <el-tag v-else-if="scope.row.attritionLevel === 2" type="info">低风险</el-tag>
+          <el-tag v-else-if="scope.row.attritionLevel === 3" type="warning">中风险</el-tag>
+          <el-tag v-else-if="scope.row.attritionLevel === 4" type="danger">高风险</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="意向度" align="center" prop="intentionDegree">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.intentionDegree !== null && scope.row.intentionDegree !== undefined && scope.row.intentionDegree !== ''">
+            {{ scope.row.intentionDegree }}
+          </el-tag>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="关注点" align="center" prop="customerFocusJson" width="220">
+        <template slot-scope="scope">
+          <div v-if="parseFocusPoints(scope.row.customerFocusJson).length" style="text-align: left;">
+            <el-tooltip
+              v-for="(item, index) in parseFocusPoints(scope.row.customerFocusJson).slice(0, 2)"
+              :key="index"
+              placement="top"
+              effect="light"
+            >
+              <div slot="content" style="max-width: 420px; word-break: break-word;">
+                {{ item }}
+              </div>
+              <el-tag style="margin: 0 6px 6px 0; max-width: 100%; background: #fff; border: 1px solid #dcdfe6; color: #606266;">
+                {{ shortenText(item, 14) }}
+              </el-tag>
+            </el-tooltip>
+            <el-tooltip
+              v-if="parseFocusPoints(scope.row.customerFocusJson).length > 2"
+              placement="top"
+              effect="light"
+            >
+              <div slot="content" style="max-width: 420px;">
+                <div
+                  v-for="(item, idx) in parseFocusPoints(scope.row.customerFocusJson).slice(2)"
+                  :key="'focus-more-' + idx"
+                  style="margin-bottom: 4px;"
+                >
+                  {{ item }}
+                </div>
+              </div>
+              <el-tag style="margin: 0 6px 6px 0; background: #fff; border: 1px solid #dcdfe6; color: #606266;">
+                +{{ parseFocusPoints(scope.row.customerFocusJson).length - 2 }}
+              </el-tag>
+            </el-tooltip>
+          </div>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
       <el-table-column label="状态" align="center" prop="status" width="120px" >
         <template slot-scope="scope">
           <dict-tag :options="statusOptions" :value="scope.row.status"/>
@@ -422,6 +477,13 @@
           <el-tag v-else type="info"> 未绑定</el-tag>
         </template>
       </el-table-column>
+      <el-table-column label="是否下载APP" width="100px" align="center" fixed="right">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.isDownloadApp === 1 ? 'success' : 'info'">
+            {{ scope.row.isDownloadApp === 1 ? '已下载' : '未下载' }}
+          </el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="修改" align="center" class-name="small-padding fixed-width" width="120px" fixed="right">
         <template slot-scope="scope">
           <el-button
@@ -500,6 +562,13 @@
             @click="healthHandledetails(scope.row)"
           >健康档案
           </el-button>
+          <el-button
+          v-if="scope.row.attritionLevel !== null && scope.row.attritionLevel !== undefined"
+          size="mini"
+          type="text"
+          @click="openAiDrawer(scope.row)"
+          v-hasPermi="['qw:externalContact:analyze:list']"
+        >AI 分析</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -793,6 +862,20 @@
       :visible.sync="showUser.open">
       <userDetails  ref="userDetails" />
     </el-drawer>
+    <el-drawer
+    size="75%"
+    :title="aiAnalyze.title"
+    :visible.sync="aiAnalyze.open"
+    append-to-body
+  >
+    <customer-detail
+      ref="customerAiDetail"
+      :analyze-user-id="aiAnalyze.userId"
+      :analyze-external-user-id="aiAnalyze.externalUserId"
+      :analyze-corp-id="aiAnalyze.corpId"
+      :customer-row="aiAnalyze.customerRow"
+    />
+  </el-drawer>
   </div>
 </template>
 
@@ -829,10 +912,11 @@ import {editTalk} from "@/api/qw/externalContactInfo";
 import healthRecordDetails from '@/views/store/components/healthRecordDetails.vue'
 import userDetails from '@/views/store/components/userDetails.vue';
 import PaginationMore from "../../../components/PaginationMore/index.vue";
+import customerDetail from './customerDetail.vue';
 
 export default {
   name: "deptExternalContact",
-  components:{PaginationMore, mycustomer,customerDetails,SopDialog,selectUser,info,healthRecordDetails,userDetails},
+  components:{PaginationMore, mycustomer,customerDetails,SopDialog,selectUser,info,healthRecordDetails,userDetails,customerDetail},
   data() {
     return {
 
@@ -1045,7 +1129,15 @@ export default {
       },
       tongueReportParams: {
         userId: null,
-      }
+      },
+      aiAnalyze: {
+        title: "AI 分析",
+        open: false,
+        userId: null,
+        externalUserId: null,
+        corpId: null,
+        customerRow: null,
+      },
     };
   },
   created() {
@@ -1095,7 +1187,41 @@ export default {
 
   },
   methods: {
-
+    shortenText(text, maxLen = 16) {
+      const str = text == null ? '' : String(text);
+      if (str.length <= maxLen) return str;
+      return str.slice(0, maxLen) + '...';
+    },
+    parseFocusPoints(value) {
+      if (!value) return [];
+      if (Array.isArray(value)) {
+        return value.map(item => String(item).trim()).filter(Boolean);
+      }
+      if (typeof value === 'string') {
+        const raw = value.trim();
+        if (!raw) return [];
+        try {
+          const parsed = JSON.parse(raw);
+          if (Array.isArray(parsed)) {
+            return parsed.map(item => String(item).trim()).filter(Boolean);
+          }
+          if (typeof parsed === 'string') {
+            return [parsed.trim()].filter(Boolean);
+          }
+        } catch (e) {
+          // ignore parse error and use raw fallback
+        }
+        return [raw.replace(/^\[|\]$/g, '').replace(/["']/g, '').trim()].filter(Boolean);
+      }
+      return [String(value).trim()].filter(Boolean);
+    },
+    openAiDrawer(row) {
+      this.aiAnalyze.userId = row.userId;
+      this.aiAnalyze.externalUserId = row.operUserid ;
+      this.aiAnalyze.corpId = row.corpId ;
+      this.aiAnalyze.customerRow = row;
+      this.aiAnalyze.open = true;
+    },
     onQwUserNameClear() {
       this.queryParams.qwUserId = null;  // 同时清空 qwUserId
     },

+ 149 - 13
src/views/qw/externalContact/index.vue

@@ -50,7 +50,7 @@
             class="suggestion-item"
             @click="selectQwUser(item.dictValue,item.dictLabel)"
           >
-            {{ item.dictLabel }}
+            {{ item.dictLabel }} ({{(item.dictValue)}})
           </div>
         </div>
       </el-form-item>
@@ -386,11 +386,6 @@
       <el-table-column label="备注" align="center" prop="remark" />
       <el-table-column label="描述信息" align="center" prop="description" />
       <el-table-column label="标签" align="center" prop="tagIdsName" width="300px">
-<!--        <template slot-scope="scope">-->
-<!--          <div v-for="name in scope.row.tagIdsName"  style="display: inline;">-->
-<!--          <el-tag type="success">{{ name }}</el-tag>-->
-<!--          </div>-->
-<!--        </template>-->
         <template slot-scope="scope">
           <div class="tag-container">
             <div class="tag-list">
@@ -406,6 +401,62 @@
           </div>
         </template>
       </el-table-column>
+      <el-table-column label="流失风险" align="center" prop="attritionLevel">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.attritionLevel == null" type="info">未分析</el-tag>
+          <el-tag v-if="scope.row.attritionLevel === 0" type="info">未知</el-tag>
+          <el-tag v-else-if="scope.row.attritionLevel === 1" type="success">无风险</el-tag>
+          <el-tag v-else-if="scope.row.attritionLevel === 2" type="info">低风险</el-tag>
+          <el-tag v-else-if="scope.row.attritionLevel === 3" type="warning">中风险</el-tag>
+          <el-tag v-else-if="scope.row.attritionLevel === 4" type="danger">高风险</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="意向度" align="center" prop="intentionDegree">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.intentionDegree !== null && scope.row.intentionDegree !== undefined && scope.row.intentionDegree !== ''">
+            {{ scope.row.intentionDegree }}
+          </el-tag>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="关注点" align="center" prop="customerFocusJson" width="220">
+        <template slot-scope="scope">
+          <div v-if="parseFocusPoints(scope.row.customerFocusJson).length" style="text-align: left;">
+            <el-tooltip
+              v-for="(item, index) in parseFocusPoints(scope.row.customerFocusJson).slice(0, 2)"
+              :key="index"
+              placement="top"
+              effect="light"
+            >
+              <div slot="content" style="max-width: 420px; word-break: break-word;">
+                {{ item }}
+              </div>
+              <el-tag style="margin: 0 6px 6px 0; max-width: 100%; background: #fff; border: 1px solid #dcdfe6; color: #606266;">
+                {{ shortenText(item, 14) }}
+              </el-tag>
+            </el-tooltip>
+            <el-tooltip
+              v-if="parseFocusPoints(scope.row.customerFocusJson).length > 2"
+              placement="top"
+              effect="light"
+            >
+              <div slot="content" style="max-width: 420px;">
+                <div
+                  v-for="(item, idx) in parseFocusPoints(scope.row.customerFocusJson).slice(2)"
+                  :key="'focus-more-' + idx"
+                  style="margin-bottom: 4px;"
+                >
+                  {{ item }}
+                </div>
+              </div>
+              <el-tag style="margin: 0 6px 6px 0; background: #fff; border: 1px solid #dcdfe6; color: #606266;">
+                +{{ parseFocusPoints(scope.row.customerFocusJson).length - 2 }}
+              </el-tag>
+            </el-tooltip>
+          </div>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
       <el-table-column label="是否回复" align="center" prop="isReply" width="120px" >
         <template slot-scope="scope">
           <span v-if="scope.row.isReply === 1"><el-tag type="success">已回复</el-tag></span>
@@ -471,10 +522,17 @@
       </el-table-column>
       <el-table-column label="是否绑定会员" width="100px" align="center" fixed="right">
         <template slot-scope="scope">
-          <el-tag v-if="scope.row.fsUserId" >已绑定</el-tag>
+          <el-tag v-if="scope.row.fsUserId" >已绑定<br/>{{scope.row.fsUserId}}</el-tag>
           <el-tag v-else type="info"> 未绑定</el-tag>
         </template>
       </el-table-column>
+      <el-table-column label="是否下载APP" width="100px" align="center" fixed="right">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.isDownloadApp === 1 ? 'success' : 'info'">
+            {{ scope.row.isDownloadApp === 1 ? '已下载' : '未下载' }}
+          </el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="修改" align="center" class-name="small-padding fixed-width" width="120px" fixed="right">
         <template slot-scope="scope">
           <el-button
@@ -564,6 +622,13 @@
           >
             修改状态
           </el-button>
+          <el-button
+                v-if="scope.row.attritionLevel !== null && scope.row.attritionLevel !== undefined"
+                size="mini"
+                type="text"
+                @click="openAiDrawer(scope.row)"
+                v-hasPermi="['qw:externalContact:analyze:list']"
+              >AI 分析</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -575,7 +640,20 @@
       :limit.sync="queryParams.pageSize"
       @pagination="getList"
     />
-
+    <el-drawer
+    size="75%"
+    :title="aiAnalyze.title"
+    :visible.sync="aiAnalyze.open"
+    append-to-body
+  >
+    <customer-detail
+      ref="customerAiDetail"
+      :analyze-user-id="aiAnalyze.userId"
+      :analyze-external-user-id="aiAnalyze.externalUserId"
+      :analyze-corp-id="aiAnalyze.corpId"
+      :customer-row="aiAnalyze.customerRow"
+    />
+  </el-drawer>
     <el-drawer size="75%" :title="show.title" :visible.sync="show.open">
       <customer-details  ref="customerDetails" @refreshList="refreshList"/>
     </el-drawer>
@@ -717,7 +795,7 @@
     <el-dialog title="批量移除标签" :visible.sync="tagDelOpen" width="800px" append-to-body>
       <div>搜索标签:
         <el-input v-model="queryTagParams.name" placeholder="请输入标签名称" clearable size="small" style="width: 200px;margin-right: 10px" />
-        <el-button type="primary" icon="el-icon-search" size="mini" @click="getPageListTagGroup()">搜索</el-button>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleSearchTags(queryTagParams.name)">搜索</el-button>
         <el-button type="primary" icon="el-icon-plus" size="mini" @click="cancelSearchTags">重置</el-button>
       </div>
       <el-form ref="form" :model="addTagForm"  label-width="80px">
@@ -956,9 +1034,10 @@ import PaginationMore from "../../../components/PaginationMore/index.vue";
 import userDetails from '@/views/store/components/userDetails.vue';
 import {courseList, videoList} from "@/api/course/courseRedPacketLog";
 import Collection from './collection.vue';
+import customerDetail from './customerDetail.vue';
 export default {
   name: "ExternalContact",
-  components:{PaginationMore, mycustomer,customerDetails,SopDialog,selectUser,info,userDetails,collection},
+  components:{PaginationMore, mycustomer,customerDetails,customerDetail,SopDialog,selectUser,info,userDetails,collection},
   data() {
     return {
       projectOptions: [],
@@ -1188,6 +1267,14 @@ export default {
       rules: {
       },
       userId:null,
+      aiAnalyze: {
+        title: "AI 分析",
+        open: false,
+        userId: null,
+        externalUserId: null,
+        corpId: null,
+        customerRow: null,
+      },
     };
   },
   created() {
@@ -1240,6 +1327,41 @@ export default {
 
   },
   methods: {
+    shortenText(text, maxLen = 16) {
+      const str = text == null ? '' : String(text);
+      if (str.length <= maxLen) return str;
+      return str.slice(0, maxLen) + '...';
+    },
+    parseFocusPoints(value) {
+      if (!value) return [];
+      if (Array.isArray(value)) {
+        return value.map(item => String(item).trim()).filter(Boolean);
+      }
+      if (typeof value === 'string') {
+        const raw = value.trim();
+        if (!raw) return [];
+        try {
+          const parsed = JSON.parse(raw);
+          if (Array.isArray(parsed)) {
+            return parsed.map(item => String(item).trim()).filter(Boolean);
+          }
+          if (typeof parsed === 'string') {
+            return [parsed.trim()].filter(Boolean);
+          }
+        } catch (e) {
+          // ignore parse error and use raw fallback
+        }
+        return [raw.replace(/^\[|\]$/g, '').replace(/["']/g, '').trim()].filter(Boolean);
+      }
+      return [String(value).trim()].filter(Boolean);
+    },
+    openAiDrawer(row) {
+      this.aiAnalyze.userId = row.userId;
+      this.aiAnalyze.externalUserId = row.operUserid ;
+      this.aiAnalyze.corpId = row.corpId ;
+      this.aiAnalyze.customerRow = row;
+      this.aiAnalyze.open = true;
+    },
     /** 重粉查看操作 */
     showLog(row) {
       this.log.queryParams.fsUserId = row.fsUserId;
@@ -1290,7 +1412,10 @@ export default {
       this.collection.open = false;
     },
     onQwUserNameClear() {
+      this.queryParams.qwUserName = '';  // 清空 qwUserName
       this.queryParams.qwUserId = null;  // 同时清空 qwUserId
+      this.qwUserSuggestions = [];       // 清空下拉建议
+      this.showQwUserDropdown = false;   // 隐藏下拉框
     },
     // 搜索企微用户
     searchQwUser(query) {
@@ -1723,7 +1848,6 @@ export default {
           if(this.tagGroupList[i].tag[x].isSelected==true){
             this.addTagForm.tagIds.push(this.tagGroupList[i].tag[x].tagId)
           }
-
         }
       }
       if(this.addTagForm.tagIds==[]||this.addTagForm.tagIds==null||this.addTagForm.tagIds==""){
@@ -1733,10 +1857,18 @@ export default {
       this.addTagForm.corpId=this.queryParams.corpId
       this.addTagForm.userIds=this.ids;
       this.addTagForm.filter = this.tagFilter;
-      let obj = JSON.parse(JSON.stringify(this.queryParams))
-      if(obj.tagIds !== null && obj.tagIds !== undefined && obj.tagIds !== ''){
+
+      // 修改这里:正确处理参数对象
+      let obj = JSON.parse(JSON.stringify(this.queryParams));
+
+      // 将逗号分隔的字符串转换为数组
+      if(obj.tagIds && typeof obj.tagIds === 'string') {
         obj.tagIds = obj.tagIds.split(",");
       }
+      if(obj.outTagIds && typeof obj.outTagIds === 'string') {
+        obj.outTagIds = obj.outTagIds.split(",");
+      }
+
       this.addTagForm.param = obj;
 
       let loadingRock = this.$loading({
@@ -1978,8 +2110,12 @@ export default {
       this.resetForm("queryForm");
       this.queryParams.transferStatus=null;
       this.queryParams.corpId= this.myQwCompanyList[0].dictValue;
+      this.queryParams.qwUserName = '';      // 清空销售企微昵称
+      this.queryParams.qwUserId = null;      // 清空销售企微ID
       this.selectTags=[];
       this.outSelectTags=[];
+      this.qwUserSuggestions = [];          // 清空下拉建议
+      this.showQwUserDropdown = false;      // 隐藏下拉框
 	   this.createTime=null;
 	  this.queryParams.sTime=null;
 	  this.queryParams.eTime=null;

+ 141 - 2
src/views/qw/externalContact/myExternalContact.vue

@@ -11,6 +11,15 @@
                 />
               </el-select>
       </el-form-item>
+      <el-form-item label="企微客户ID" prop="id">
+        <el-input
+          v-model="queryParams.id"
+          placeholder="请输入企微客户ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
       <el-form-item label="客户名称" prop="name">
         <el-input
           v-model="queryParams.name"
@@ -375,6 +384,62 @@
           </div>
         </template>
       </el-table-column>
+      <el-table-column label="流失风险" align="center" prop="attritionLevel">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.attritionLevel == null" type="info">未分析</el-tag>
+          <el-tag v-if="scope.row.attritionLevel === 0" type="info">未知</el-tag>
+          <el-tag v-else-if="scope.row.attritionLevel === 1" type="success">无风险</el-tag>
+          <el-tag v-else-if="scope.row.attritionLevel === 2" type="info">低风险</el-tag>
+          <el-tag v-else-if="scope.row.attritionLevel === 3" type="warning">中风险</el-tag>
+          <el-tag v-else-if="scope.row.attritionLevel === 4" type="danger">高风险</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="意向度" align="center" prop="intentionDegree">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.intentionDegree !== null && scope.row.intentionDegree !== undefined && scope.row.intentionDegree !== ''">
+            {{ scope.row.intentionDegree }}
+          </el-tag>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="关注点" align="center" prop="customerFocusJson" width="220">
+        <template slot-scope="scope">
+          <div v-if="parseFocusPoints(scope.row.customerFocusJson).length" style="text-align: left;">
+            <el-tooltip
+              v-for="(item, index) in parseFocusPoints(scope.row.customerFocusJson).slice(0, 2)"
+              :key="index"
+              placement="top"
+              effect="light"
+            >
+              <div slot="content" style="max-width: 420px; word-break: break-word;">
+                {{ item }}
+              </div>
+              <el-tag style="margin: 0 6px 6px 0; max-width: 100%; background: #fff; border: 1px solid #dcdfe6; color: #606266;">
+                {{ shortenText(item, 14) }}
+              </el-tag>
+            </el-tooltip>
+            <el-tooltip
+              v-if="parseFocusPoints(scope.row.customerFocusJson).length > 2"
+              placement="top"
+              effect="light"
+            >
+              <div slot="content" style="max-width: 420px;">
+                <div
+                  v-for="(item, idx) in parseFocusPoints(scope.row.customerFocusJson).slice(2)"
+                  :key="'focus-more-' + idx"
+                  style="margin-bottom: 4px;"
+                >
+                  {{ item }}
+                </div>
+              </div>
+              <el-tag style="margin: 0 6px 6px 0; background: #fff; border: 1px solid #dcdfe6; color: #606266;">
+                +{{ parseFocusPoints(scope.row.customerFocusJson).length - 2 }}
+              </el-tag>
+            </el-tooltip>
+          </div>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
       <el-table-column label="是否回复" align="center" prop="isReply" width="120px" >
         <template slot-scope="scope">
           <span v-if="scope.row.isReply === 1"><el-tag type="success">已回复</el-tag></span>
@@ -441,10 +506,17 @@
       </el-table-column>
       <el-table-column label="是否绑定会员" width="100px" align="center" fixed="right">
         <template slot-scope="scope">
-          <el-tag v-if="scope.row.fsUserId" >已绑定</el-tag>
+          <el-tag v-if="scope.row.fsUserId" >已绑定<br/>{{scope.row.fsUserId}}</el-tag>
           <el-tag v-else type="info"> 未绑定</el-tag>
         </template>
       </el-table-column>
+      <el-table-column label="是否下载APP" width="100px" align="center" fixed="right">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.isDownloadApp === 1 ? 'success' : 'info'">
+            {{ scope.row.isDownloadApp === 1 ? '已下载' : '未下载' }}
+          </el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="修改" align="center" class-name="small-padding fixed-width" width="120px" fixed="right">
         <template slot-scope="scope">
           <el-button
@@ -546,6 +618,13 @@
         >
           修改状态
         </el-button>
+        <el-button
+                v-if="scope.row.attritionLevel !== null && scope.row.attritionLevel !== undefined"
+                size="mini"
+                type="text"
+                @click="openAiDrawer(scope.row)"
+                v-hasPermi="['qw:externalContact:analyze:list']"
+              >AI 分析</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -964,6 +1043,20 @@
 	 :title="show.title" :visible.sync="show.open">
 	  <info  ref="Details" />
 	</el-drawer>
+  <el-drawer
+  size="75%"
+  :title="aiAnalyze.title"
+  :visible.sync="aiAnalyze.open"
+  append-to-body
+>
+  <customer-detail
+    ref="customerAiDetail"
+    :analyze-user-id="aiAnalyze.userId"
+    :analyze-external-user-id="aiAnalyze.externalUserId"
+    :analyze-corp-id="aiAnalyze.corpId"
+    :customer-row="aiAnalyze.customerRow"
+  />
+</el-drawer>
     <el-dialog :title="user.title" :visible.sync="user.open" width="800px" append-to-body>
       <selectUser ref="selectUser" @bindMiniCustomerId="bindMiniCustomerId"></selectUser>
     </el-dialog>
@@ -1027,11 +1120,20 @@ import PaginationMore from "../../../components/PaginationMore/index.vue";
 import Collection from './collection.vue';
 import {courseList, videoList} from "@/api/course/courseRedPacketLog";
 import userDetails from '@/views/store/components/userDetails.vue';
+import customerDetail from './customerDetail.vue';
 export default {
   name: "ExternalContact",
-  components:{PaginationMore, mycustomer,customerDetails,SopDialog,selectUser,info,Collection,userDetails},
+  components:{PaginationMore, mycustomer,customerDetails,SopDialog,selectUser,info,Collection,userDetails,customerDetail},
   data() {
     return {
+      aiAnalyze: {
+        title: "AI 分析",
+        open: false,
+        userId: null,
+        externalUserId: null,
+        corpId: null,
+        customerRow: null,
+      },
       member:{
         title:"客户详情",
         open:false,
@@ -1199,6 +1301,7 @@ export default {
       queryParams: {
         pageNum: 1,
         pageSize: 10,
+        id:  null,
         userId: null,
         qwUserName:null,
         externalUserId: null,
@@ -1297,6 +1400,42 @@ export default {
 
   },
   methods: {
+    shortenText(text, maxLen = 16) {
+      const str = text == null ? '' : String(text);
+      if (str.length <= maxLen) return str;
+      return str.slice(0, maxLen) + '...';
+    },
+    parseFocusPoints(value) {
+      if (!value) return [];
+      if (Array.isArray(value)) {
+        return value.map(item => String(item).trim()).filter(Boolean);
+      }
+      if (typeof value === 'string') {
+        const raw = value.trim();
+        if (!raw) return [];
+        try {
+          const parsed = JSON.parse(raw);
+          if (Array.isArray(parsed)) {
+            return parsed.map(item => String(item).trim()).filter(Boolean);
+          }
+          if (typeof parsed === 'string') {
+            return [parsed.trim()].filter(Boolean);
+          }
+        } catch (e) {
+          // ignore parse error and use raw fallback
+        }
+        return [raw.replace(/^\[|\]$/g, '').replace(/["']/g, '').trim()].filter(Boolean);
+      }
+      return [String(value).trim()].filter(Boolean);
+    },
+    openAiDrawer(row) {
+      this.aiAnalyze.userId = row.userId;
+      this.aiAnalyze.externalUserId = row.operUserid ;
+      this.aiAnalyze.corpId = row.corpId ;
+      this.aiAnalyze.customerRow = row;
+      console.log(this.aiAnalyze);
+      this.aiAnalyze.open = true;
+    },
     /** 重粉查看操作 */
     showLog(row) {
       this.log.queryParams.fsUserId = row.fsUserId;

+ 11 - 2
src/views/qw/externalContactTransfer/index.vue

@@ -486,10 +486,19 @@ export default {
     },
     /** 重置按钮操作 */
     resetQuery() {
-      this.selectTags=[];
-      this.dateRange=null;
+      this.selectTags = [];
+      this.dateRange = null;
+      this.queryParams.transferStatus = null; // 手动清空转接状态
+      this.queryParams.pageNum = 1;
       this.changeTime();
       this.resetForm("queryForm");
+      // 确保重置后再次清空(防止resetForm没有清空)
+      this.$nextTick(() => {
+        if (this.$refs.queryForm) {
+          this.$refs.queryForm.resetFields();
+        }
+        this.queryParams.transferStatus = null;
+      });
       this.handleQuery();
     },
     // 多选框选中数据

+ 21 - 6
src/views/qw/friendWelcome/indexNew.vue

@@ -1089,7 +1089,7 @@ export default {
             return false;
           }
         });
-     
+
     },
 
     //取消附件
@@ -1157,12 +1157,27 @@ export default {
     },
     /** 重置按钮操作 */
     resetQuery() {
-      this.queryParams.createTime = null;
+      // 重置整个查询表单
       this.resetForm("queryForm");
-      this.queryParams.corpId= this.myQwCompanyList[0].dictValue
-      getQwAllUserList(this.queryParams.corpId).then(response => {
-        this.companyUserList = response.data;
-      });
+
+      // 重置 corpId 为公司列表第一个
+      this.queryParams.corpId = this.myQwCompanyList[0]?.dictValue || null;
+
+      // 重置 qwUserIds 为空数组
+      this.queryParams.qwUserIds = [];
+
+      // 重置创建时间和更新时间
+      this.queryParams.createTime = null;
+      this.queryParams.updateTime = null;
+
+      // 重新获取用户列表
+      if (this.queryParams.corpId) {
+        getQwAllUserList(this.queryParams.corpId).then(response => {
+          this.companyUserList = response.data;
+        });
+      }
+
+      // 执行查询
       this.handleQuery();
     },
     // 多选框选中数据

+ 3 - 3
src/views/qw/sopTemp/index.vue

@@ -360,7 +360,7 @@
         <el-button type="primary" @click="updateRedData" :disabled="redData.loading">保 存</el-button>
       </div>
     </el-dialog>
-    
+
     <el-dialog title="批量编辑官方群发" :visible.sync="official.open" width="500px" append-to-body>
        <el-form :model="officialForm" ref="officialForm" :inline="true">
          <el-form-item  label="官方群发" prop="isOfficial">
@@ -606,9 +606,9 @@ export default {
       this.openIsAtAllOptions = response.data;
     });
 
-    getSelectableRange().then(e => {
+/*    getSelectableRange().then(e => {
       this.startTimeRange = e.data;
-    })
+    })*/
     this.getDicts("sys_course_project").then(response => {
       this.projectOptions = response.data;
     });

+ 1 - 1
src/views/statistics/section/channel.vue

@@ -76,7 +76,7 @@
           icon="el-icon-download"
           size="mini"
           @click="handleExport"
-          v-hasPermi="['statistics:stats:export']"
+          v-hasPermi="['system:stats:export']"
         >导出</el-button>
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>

+ 1 - 1
src/views/statistics/section/index.vue

@@ -60,7 +60,7 @@
           icon="el-icon-download"
           size="mini"
           @click="handleExport"
-          v-hasPermi="['statistics:stats:export']"
+          v-hasPermi="['system:stats:export']"
         >导出</el-button>
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>

+ 1 - 1
src/views/statistics/section/inline.vue

@@ -40,7 +40,7 @@
           icon="el-icon-download"
           size="mini"
           @click="handleExport"
-          v-hasPermi="['statistics:stats:export']"
+          v-hasPermi="['system:stats:export']"
         >导出</el-button>
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>

+ 1 - 1
src/views/statistics/section/today.vue

@@ -76,7 +76,7 @@
           icon="el-icon-download"
           size="mini"
           @click="handleExport"
-          v-hasPermi="['statistics:stats:export']"
+          v-hasPermi="['system:stats:export']"
         >导出</el-button>
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>

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

@@ -91,7 +91,7 @@
           icon="el-icon-download"
           size="mini"
           @click="handleExport"
-          v-hasPermi="['company:approval:export']"
+          v-hasPermi="['system:approval:export']"
         >导出</el-button>
       </el-col>
 	  <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -129,7 +129,7 @@
             type="text"
             icon="el-icon-edit"
             @click="viewMode=false;handleUpdate(scope.row)"
-            v-hasPermi="['company:approval:edit']"
+            v-hasPermi="['system:approval:edit']"
           >审批</el-button>
           <el-button
             v-if="scope.row.approvalStatus != 0"
@@ -137,7 +137,7 @@
             type="text"
             icon="el-icon-edit"
             @click="viewMode=true;handleUpdate(scope.row)"
-            v-hasPermi="['company:approval:edit']"
+            v-hasPermi="['system:approval:edit']"
           >查看</el-button>
         </template>
       </el-table-column>

+ 3 - 3
src/views/users/user/transferLog.vue

@@ -80,7 +80,7 @@
           icon="el-icon-download"
           size="mini"
           @click="handleExport"
-          v-hasPermi="['company:approval:export']"
+          v-hasPermi="['system:approval:export']"
         >导出</el-button>
       </el-col>
 	  <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -119,7 +119,7 @@
             type="text"
             icon="el-icon-edit"
             @click="viewMode=false;handleUpdate(scope.row)"
-            v-hasPermi="['company:approval:edit']"
+            v-hasPermi="['system:approval:edit']"
           >撤回</el-button>
           <el-button
             v-if="scope.row.approvalStatus != 0"
@@ -127,7 +127,7 @@
             type="text"
             icon="el-icon-edit"
             @click="viewMode=true;handleUpdate(scope.row)"
-            v-hasPermi="['company:approval:edit']"
+            v-hasPermi="['system:approval:edit']"
           >查看</el-button>
         </template>
       </el-table-column>

Некоторые файлы не были показаны из-за большого количества измененных файлов