Ver Fonte

外呼/渠道活码

三七 há 12 horas atrás
pai
commit
f031810f81

+ 2 - 0
.env.prod-sxjz

@@ -23,3 +23,5 @@ VUE_APP_PROJECT_FROM=sxjz
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true
+
+VUE_APP_IPAD_IS_NEW_VERSION = true

+ 53 - 0
src/api/aiSopCall/aiSopCall.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询AI 外呼角色列表
+export function listAiSopCall(query) {
+  return request({
+    url: '/aiSopCall/aiSopCall/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询AI 外呼角色详细
+export function getAiSopCall(id) {
+  return request({
+    url: '/aiSopCall/aiSopCall/' + id,
+    method: 'get'
+  })
+}
+
+// 新增AI 外呼角色
+export function addAiSopCall(data) {
+  return request({
+    url: '/aiSopCall/aiSopCall',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改AI 外呼角色
+export function updateAiSopCall(data) {
+  return request({
+    url: '/aiSopCall/aiSopCall',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除AI 外呼角色
+export function delAiSopCall(id) {
+  return request({
+    url: '/aiSopCall/aiSopCall/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出AI 外呼角色
+export function exportAiSopCall(query) {
+  return request({
+    url: '/aiSopCall/aiSopCall/export',
+    method: 'get',
+    params: query
+  })
+}

+ 61 - 0
src/api/aiSopCall/aiSopCallKnowledgeBase.js

@@ -0,0 +1,61 @@
+import request from '@/utils/request'
+
+// 查询AI外呼知识库列表
+export function listAiSopCallKnowledgeBase(query) {
+  return request({
+    url: '/company/aiSopCallKnowledgeBase/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询AI外呼知识库详细
+export function getAiSopCallKnowledgeBase(id) {
+  return request({
+    url: '/company/aiSopCallKnowledgeBase/' + id,
+    method: 'get'
+  })
+}
+
+// 新增AI外呼知识库
+export function addAiSopCallKnowledgeBase(data) {
+  return request({
+    url: '/company/aiSopCallKnowledgeBase',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改AI外呼知识库
+export function updateAiSopCallKnowledgeBase(data) {
+  return request({
+    url: '/company/aiSopCallKnowledgeBase',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除AI外呼知识库
+export function delAiSopCallKnowledgeBase(id) {
+  return request({
+    url: '/company/aiSopCallKnowledgeBase/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出AI外呼知识库
+export function exportAiSopCallKnowledgeBase(query) {
+  return request({
+    url: '/company/aiSopCallKnowledgeBase/export',
+    method: 'get',
+    params: query
+  })
+}
+// 查询AI外呼知识库列表
+export function idAndNameList(query) {
+  return request({
+    url: '/company/aiSopCallKnowledgeBase/idAndNameList',
+    method: 'get',
+    params: query
+  })
+}

+ 61 - 0
src/api/aiSopCall/aiSopCallRole.js

@@ -0,0 +1,61 @@
+import request from '@/utils/request'
+
+// 查询AI外呼角色列表
+export function listRole(query) {
+  return request({
+    url: '/aiSopCall/role/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询AI外呼角色详细
+export function getRole(id) {
+  return request({
+    url: '/aiSopCall/role/' + id,
+    method: 'get'
+  })
+}
+
+// 新增AI外呼角色
+export function addRole(data) {
+  return request({
+    url: '/aiSopCall/role',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改AI外呼角色
+export function updateRole(data) {
+  return request({
+    url: '/aiSopCall/role',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除AI外呼角色
+export function delRole(id) {
+  return request({
+    url: '/aiSopCall/role/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出AI外呼角色
+export function exportRole(query) {
+  return request({
+    url: '/aiSopCall/role/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 复制AI外呼角色
+export function copyRole(id) {
+  return request({
+    url: '/aiSopCall/role/copyRole/' + id,
+    method: 'get'
+  })
+}

+ 53 - 0
src/api/aiSopCall/aiSopCallStaffModels.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询外呼模型模型列表
+export function listAiSopCallStaffModels(query) {
+  return request({
+    url: '/aiSopCallStaffModels/aiSopCallStaffModels/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询外呼模型模型详细
+export function getAiSopCallStaffModels(id) {
+  return request({
+    url: '/aiSopCallStaffModels/aiSopCallStaffModels/' + id,
+    method: 'get'
+  })
+}
+
+// 新增外呼模型模型
+export function addAiSopCallStaffModels(data) {
+  return request({
+    url: '/aiSopCallStaffModels/aiSopCallStaffModels',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改外呼模型模型
+export function updateAiSopCallStaffModels(data) {
+  return request({
+    url: '/aiSopCallStaffModels/aiSopCallStaffModels',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除外呼模型模型
+export function delAiSopCallStaffModels(id) {
+  return request({
+    url: '/aiSopCallStaffModels/aiSopCallStaffModels/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出外呼模型模型
+export function exportAiSopCallStaffModels(query) {
+  return request({
+    url: '/aiSopCallStaffModels/aiSopCallStaffModels/export',
+    method: 'get',
+    params: query
+  })
+}

+ 97 - 0
src/api/aiSopCall/aiSopCallUser.js

@@ -0,0 +1,97 @@
+import request from '@/utils/request'
+
+// 查询员工角色列表
+export function listAiSopCallUser(query) {
+  return request({
+    url: '/aiSopCallUser/aiSopCallUser/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询员工角色详细
+export function getAiSopCallUser(id) {
+  return request({
+    url: '/aiSopCallUser/aiSopCallUser/' + id,
+    method: 'get'
+  })
+}
+
+// 新增员工角色
+export function addAiSopCallUser(data) {
+  return request({
+    url: '/aiSopCallUser/aiSopCallUser',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改员工角色
+export function updateAiSopCallUser(data) {
+  return request({
+    url: '/aiSopCallUser/aiSopCallUser',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除员工角色
+export function delAiSopCallUser(id) {
+  return request({
+    url: '/aiSopCallUser/aiSopCallUser/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出员工角色
+export function exportAiSopCallUser(query) {
+  return request({
+    url: '/aiSopCallUser/aiSopCallUser/export',
+    method: 'get',
+    params: query
+  })
+}
+
+
+// 获取模型列表(分页查询)
+export function getMode(params) {
+  return request({
+    url: '/aiSopCallUser/aiSopCallUser/getMode',
+    method: 'get',
+    params: params
+  })
+}
+
+
+// 获取音色列表
+export function getVoicesDict() {
+  return request({
+    url: '/aiSopCallUser/aiSopCallUser/getVoicesDict',
+    method: 'get',
+  })
+}
+
+// 获取指定模型的语音标识列表
+export function getVoicesDictByModelId(params) {
+  return request({
+    url: '/aiSopCallUser/aiSopCallUser/getVoicesDictByModelId',
+    method: 'get',
+    params: params
+  })
+}
+
+// 保存本地语音
+export function saveUploadOssFile(file, fileName) {
+  const formData = new FormData();
+  formData.append('file', file);
+  formData.append('fileName', fileName);
+
+  return request({
+    url: '/aiSopCallUser/aiSopCallUser/saveUploadOssFile',
+    method: 'post',
+    data: formData,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  })
+}

+ 44 - 1
src/api/qw/QwSopTempVoice.js

@@ -3,9 +3,52 @@ import request from '@/utils/request'
 // 查询销售语音文件列表
 export function getQwSopTempVoiceCompanyUser(data) {
     return request({
-        url: '/qw/SopTempVoice/getQwUserVoice',
+        url: '/qw/QwSopTempVoice/getQwUserVoice',
         method: 'post',
         data: data
     })
 }
 
+// 查询模板对应的销售语音文件列表
+export function listQwSopTempVoice(query) {
+    return request({
+        url: '/qw/QwSopTempVoice/list',
+        method: 'get',
+        params: query
+    })
+}
+
+
+// 新增模板对应的销售语音文件
+export function addQwSopTempVoice(data) {
+    return request({
+        url: '/qw/QwSopTempVoice',
+        method: 'post',
+        data: data
+    })
+}
+
+export function removeSidebar(data) {
+    return request({
+        url: '/qw/QwSopTempVoice/removeSidebar',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改模板对应的销售语音文件
+export function updateQwSopTempVoice(data) {
+    return request({
+        url: '/qw/QwSopTempVoice',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除模板对应的销售语音文件
+export function delQwSopTempVoice(id) {
+    return request({
+        url: '/qw/QwSopTempVoice/' + id,
+        method: 'delete'
+    })
+}

+ 24 - 0
src/api/qw/externalContact.js

@@ -316,6 +316,7 @@ export function getCustomerCourseSop(query) {
 }
 
 
+
 export function batchUpdateExternalContactNotes(data) {
   return request({
     url: '/qw/externalContact/batchUpdateExternalContactNotes',
@@ -359,3 +360,26 @@ export function companyTransfer(data) {
     data: data
   })
 }
+
+/**
+ * 查询销售的企微客户数据
+ */
+export function getSalesAppDownloadStats(query) {
+    return request({
+        url: '/qw/externalContact/getSalesAppDownloadStats',
+        method: 'get',
+        params: query
+    })
+}
+
+/**
+ * 导出销售的企微客户数据
+ */
+export function exportSalesAppDownloadStats(query) {
+    return request({
+        url: '/qw/externalContact/exportSalesAppDownloadStats',
+        method: 'get',
+        params: query
+    })
+}
+

+ 99 - 0
src/api/qw/groupVoiceMsg.js

@@ -0,0 +1,99 @@
+import request from '@/utils/request'
+
+// 查询客户群发记录主列表
+export function listGroupVoiceMsg(query) {
+  return request({
+    url: '/qw/groupVoiceMsg/list',
+    method: 'get',
+    params: query
+  })
+}
+// 查询客户群发记录主列表
+export function myList(query) {
+  return request({
+    url: '/qw/groupVoiceMsg/myList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询客户群发记录主详细
+export function getGroupVoiceMsg(id) {
+  return request({
+    url: '/qw/groupVoiceMsg/' + id,
+    method: 'get'
+  })
+}
+
+
+// 新增客户群发记录主
+export function addGroupVoiceMsg(data) {
+  return request({
+    url: '/qw/groupVoiceMsg',
+    method: 'post',
+    data: data
+  })
+}
+
+/** 统计数据详情,已发送,未发送,已接收,未接收等 */
+export function countGroupVoiceMsgUser(msgId){
+  return request({
+    url: '/qw/groupVoiceMsg/getCountGroupVoiceMsgUser/' + msgId,
+    method: 'get'
+  })
+}
+
+
+/** 统计数据详情,已发送群主,送达群聊,未发送群主,未送达群聊等 */
+export function CountGroupVoiceMsgBaseUser(msgId){
+  return request({
+    url: '/qw/groupVoiceMsg/getCountGroupVoiceMsgBaseUser/' + msgId,
+    method: 'get'
+  })
+}
+
+
+/** 客户群发 发送/接收的数据详情 */
+export function CountGroupVoiceMsgUserDetails(query){
+  return request({
+    url: '/qw/groupVoiceMsg/getCountGroupVoiceMsgUserDetails',
+    method: 'get',
+    params: query
+  })
+}
+
+/** 提醒成员群发 */
+
+export function remindGroupVoiceMsg(query){
+  return request({
+    url: '/qw/groupVoiceMsg/remindGroupVoiceMsg',
+    method: 'get',
+    params: query
+  })
+}
+
+// 修改客户群发记录主
+export function updateGroupVoiceMsg(data) {
+  return request({
+    url: '/qw/groupVoiceMsg',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除客户群发记录主
+export function delGroupVoiceMsg(id) {
+  return request({
+    url: '/qw/groupVoiceMsg/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出客户群发记录主
+export function exportGroupVoiceMsg(query) {
+  return request({
+    url: '/qw/groupVoiceMsg/export',
+    method: 'get',
+    params: query
+  })
+}

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

@@ -0,0 +1,71 @@
+import request from '@/utils/request'
+
+// 查询群发成员发送任务及执行结果反馈记录列表
+export function listGroupVoiceMsgUser(query) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询群发成员发送任务及执行结果反馈记录详细
+export function getGroupVoiceMsgUser(id) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/' + id,
+    method: 'get'
+  })
+}
+
+// 新增群发成员发送任务及执行结果反馈记录
+export function addGroupVoiceMsgUser(data) {
+  return request({
+    url: '/qw/groupVoiceMsgUser',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改群发成员发送任务及执行结果反馈记录
+export function updateGroupVoiceMsgUser(data) {
+  return request({
+    url: '/qw/groupVoiceMsgUser',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除群发成员发送任务及执行结果反馈记录
+export function delGroupVoiceMsgUser(id) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出群发成员发送任务及执行结果反馈记录
+export function exportGroupVoiceMsgUser(query) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/export',
+    method: 'get',
+    params: query
+  })
+}
+
+/** 刷新/同步 客户群群发 结果 */
+export function refreshResultsGroupVoiceMsgUser(data){
+  return request({
+    url: '/qw/groupVoiceMsgUser/refreshResultsGroupVoiceMsgUser',
+    method: 'post',
+    data: data
+  })
+}
+
+/** 刷新/同步 客户群发 结果 */
+export function refreshResultsVoiceMsgUser(data){
+  return request({
+    url: '/qw/groupVoiceMsgUser/refreshResultsVoiceMsgUser',
+    method: 'post',
+    data: data
+  })
+}

+ 78 - 0
src/api/qw/groupVoiceMsgUserDetails.js

@@ -0,0 +1,78 @@
+import request from '@/utils/request'
+
+// 查询AI外呼客服呼叫详情列表
+export function listDetails(query) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/details/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询AI外呼客服呼叫详情详细
+export function getDetails(id) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/details/' + id,
+    method: 'get'
+  })
+}
+// 查询AI外呼客服呼叫详情详细
+export function getBySessionIdDetails(sessionId) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/details/queryBySessionId/' + sessionId,
+    method: 'get'
+  })
+}
+
+// 新增AI外呼客服呼叫详情
+export function addDetails(data) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/details',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改AI外呼客服呼叫详情
+export function updateDetails(data) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/details',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除AI外呼客服呼叫详情
+export function delDetails(id) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/details/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出AI外呼客服呼叫详情
+export function exportDetails(query) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/details/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出AI外呼客服呼叫详情
+export function statisticsAiCallDetails(data) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/details/statisticsAiCallDetails',
+    method: 'post',
+    data: data
+  })
+}
+// 导出AI外呼客服呼叫详情
+export function exportStatistics(data) {
+  return request({
+    url: '/qw/groupVoiceMsgUser/details/exportStatistics',
+    method: 'post',
+    data: data
+  })
+}
+

+ 396 - 0
src/components/chat/ChatRecordViewer.vue

@@ -0,0 +1,396 @@
+<template>
+  <div class="chat-record-viewer">
+    <div v-if="chatData && parsedMessages.length > 0" class="chat-messages">
+      <div
+        v-for="(message, index) in displayedMessages"
+        :key="index"
+        :class="['message', message.role]"
+      >
+        <div class="message-content">
+          <div
+            :class="{ 'content-collapsed': shouldCollapse(index) && !expandedMessages[index] }"
+            :style="{ maxHeight: shouldCollapse(index) && !expandedMessages[index] ? '130px' : 'none' }"
+          >
+            <pre>{{ formatMessageContent(message.content) }}</pre>
+          </div>
+
+          <!-- 将展开按钮移到消息项内部 -->
+          <div v-if="shouldCollapse(index)" class="expand-toggle">
+            <el-button
+              type="text"
+              size="mini"
+              @click="toggleMessageExpand(index)"
+              class="expand-btn"
+            >
+              {{ expandedMessages[index] ? '收起' : '展开' }}
+            </el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 正确的无数据提示 -->
+    <div v-if="!chatData || parsedMessages.length === 0" class="no-chat-data">
+      <i class="el-icon-chat-line-round"></i>
+      <p>暂无聊天记录</p>
+    </div>
+
+    <!-- 加载更多按钮应该独立于消息容器 -->
+    <div v-if="hasMoreMessages" class="load-more">
+      <el-button
+        type="text"
+        @click="loadMore"
+        :loading="loadingMore"
+      >
+        加载更多消息 ({{ remainingCount }} 条)
+      </el-button>
+    </div>
+  </div>
+</template>
+
+
+<script>
+export default {
+  name: "ChatRecordViewer",
+  props: {
+    chatData: {
+      type: [String, Object],
+      default: null
+    },
+    initialDisplayCount: {
+      type: Number,
+      default: 7
+    },
+    agentName: {
+      type: String,
+      default: '客服'
+    },
+    customerName: {
+      type: String,
+      default: '客户'
+    }
+  },
+  data() {
+    return {
+      displayCount: this.initialDisplayCount,
+      loadingMore: false,
+      expandedMessages: {}
+    };
+  },
+  computed: {
+    parsedMessages() {
+      if (!this.chatData) return [];
+
+      try {
+        // 如果是字符串,尝试解析为JSON
+        const data = typeof this.chatData === 'string'
+          ? JSON.parse(this.chatData)
+          : this.chatData;
+
+        // 根据不同的数据结构进行处理
+        if (Array.isArray(data)) {
+          return data.map(msg => ({
+            role: msg.role || 'user',
+            content: msg.content || msg.text || '',
+            time: msg.time || msg.timestamp || Date.now()
+          }));
+        } else if (typeof data === 'object') {
+          // 如果是对象,转换为数组
+          return Object.entries(data).map(([key, value]) => ({
+            role: key.includes('user') ? 'user' : 'assistant',
+            content: value,
+            time: Date.now()
+          }));
+        }
+
+        return [];
+      } catch (error) {
+        // 如果不是JSON,按纯文本处理
+        return [{
+          role: 'system',
+          content: this.chatData,
+          time: Date.now()
+        }];
+      }
+    },
+
+    displayedMessages() {
+      return this.parsedMessages.slice(-this.displayCount);
+    },
+
+    hasMoreMessages() {
+      return this.displayCount < this.parsedMessages.length;
+    },
+
+    remainingCount() {
+      return this.parsedMessages.length - this.displayCount;
+    }
+  },
+  methods: {
+    formatTime(timestamp) {
+      if (!timestamp) return '';
+      const date = new Date(timestamp);
+      return date.toLocaleTimeString('zh-CN', {
+        hour: '2-digit',
+        minute: '2-digit'
+      });
+    },
+
+    formatMessageContent(content) {
+      if (!content) return '';
+
+      // 替换[客服]为实际的客服名称
+      let formattedContent = content.replace(/\[客服\]/g, this.agentName || '客服');
+
+      // 替换[客户]为实际的客户名称
+      formattedContent = formattedContent.replace(/\[客户\]/g, this.customerName || '客户');
+
+      return formattedContent;
+    },
+
+    shouldCollapse(index) {
+      // 检查消息内容是否超过8行(默认显示8行)
+      const message = this.displayedMessages[index];
+      if (!message || !message.content) return false;
+
+      // 计算行数(简单估算)
+      const lines = message.content.split('\n').length;
+      const wordsPerLine = 50; // 假设每行约50个字符
+      const estimatedLines = Math.ceil(message.content.length / wordsPerLine);
+
+      return estimatedLines > 8 || lines > 8;
+    },
+
+    toggleMessageExpand(index) {
+      this.$set(this.expandedMessages, index, !this.expandedMessages[index]);
+    },
+
+    loadMore() {
+      this.loadingMore = true;
+      setTimeout(() => {
+        this.displayCount = Math.min(
+          this.displayCount + this.initialDisplayCount,
+          this.parsedMessages.length
+        );
+        this.loadingMore = false;
+      }, 300);
+    }
+  }
+};
+</script>
+
+<style scoped>
+.chat-record-viewer {
+  max-height: 380px;
+  overflow-y: auto;
+  overflow-x: hidden; /* 移除水平滚动条 */
+  width: 100%;
+  padding: 8px 4px; /* 压缩内边距 */
+  box-sizing: border-box;
+}
+
+.chat-messages {
+  padding: 8px; /* 压缩内边距 */
+}
+
+.message {
+  margin-bottom: 16px;
+  padding: 18px 20px;
+  border-radius: 14px;
+  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
+  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+  border: 1px solid transparent;
+  position: relative;
+  overflow: hidden;
+  max-width: 100%;
+  overflow-wrap: break-word;
+  word-wrap: break-word;
+  line-height: 1.7;
+  background: white;
+  border: 1px solid #e0e0e0;
+}
+
+.message:hover {
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+  transform: translateY(-3px);
+  border-color: #d0d0d0;
+}
+
+.message.user {
+  background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
+  border: 1px solid #2196f3;
+}
+
+.message.assistant {
+  background: linear-gradient(135deg, #f1f8e9 0%, #dcedc8 100%);
+  border: 1px solid #4caf50;
+}
+
+.message.system {
+  background: linear-gradient(135deg, #f5f5f5 0%, #eeeeee 100%);
+  border: 1px solid #9e9e9e;
+}
+
+.message-content {
+  position: relative;
+  white-space: pre-wrap;
+  word-break: break-word;
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+  max-width: 100%;
+}
+
+.message-content pre {
+  margin: 0;
+  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
+  line-height: 1.7;
+  color: #2c3e50;
+  white-space: pre-wrap;
+  word-break: break-word;
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+  font-size: 15px;
+  font-weight: 500;
+  letter-spacing: 0.3px;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+  background: linear-gradient(135deg, #ffffff 0%, #f9f9f9 100%);
+  border-radius: 10px;
+  padding: 16px 20px;
+  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.05);
+  border: 1px solid #e0e0e0;
+  transition: all 0.3s ease;
+  border-left: 3px solid #667eea;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+
+.message-content pre:hover {
+  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
+  transform: translateY(-1px);
+}
+
+.content-collapsed {
+  overflow: hidden;
+  position: relative;
+  transition: all 0.3s ease;
+}
+
+.content-collapsed pre {
+  margin: 0;
+  white-space: pre-wrap;
+  word-break: break-word;
+  font-family: inherit;
+  line-height: 1.6;
+  color: #303133;
+}
+
+.expand-toggle {
+  text-align: center;
+  padding: 10px 0;
+  margin-top: 8px;
+  position: relative;
+  z-index: 2;
+  border-radius: 8px;
+  background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  border: 1px solid #e1e8ef;
+}
+
+.expand-toggle .expand-btn {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  border-radius: 12px;
+  padding: 8px 18px;
+  font-size: 14px;
+  font-weight: 600;
+  min-height: 32px;
+  box-shadow: 0 3px 10px rgba(102, 126, 234, 0.3);
+  transition: all 0.3s ease;
+}
+
+.expand-toggle .expand-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 5px 14px rgba(102, 126, 234, 0.4);
+}
+
+.expand-btn {
+  background: #409EFF;
+  color: white;
+  border-radius: 10px; /* 减小圆角 */
+  padding: 3px 10px; /* 压缩内边距 */
+  font-size: 11px; /* 减小字体 */
+  min-height: 24px;
+}
+
+.expand-btn:hover {
+  background: #66b1ff;
+}
+
+.load-more {
+  text-align: center;
+  padding: 16px;
+  border-top: 1px solid #ebeef5;
+  background: linear-gradient(to bottom, rgba(248,250,252,0.9), rgba(248,250,252,1));
+  position: sticky;
+  bottom: 0;
+  z-index: 10;
+  margin-top: 12px;
+  border-radius: 8px;
+  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
+}
+
+.load-more .el-button {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  border-radius: 12px;
+  padding: 10px 20px;
+  font-size: 14px;
+  font-weight: 600;
+  min-height: 36px;
+  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+  transition: all 0.3s ease;
+}
+
+.load-more .el-button:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
+}
+
+.load-more .el-button:active {
+  transform: translateY(0);
+}
+
+.no-chat-data {
+  text-align: center;
+  padding: 60px 20px;
+  color: #909399;
+}
+
+.no-chat-data i {
+  font-size: 48px;
+  margin-bottom: 16px;
+  display: block;
+}
+
+.no-chat-data p {
+  margin: 0;
+  font-size: 14px;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .chat-record-viewer {
+    max-height: 300px;
+  }
+
+  .message {
+    padding: 8px;
+  }
+
+  .expand-btn {
+    padding: 2px 8px;
+    font-size: 11px;
+  }
+}
+</style>

+ 213 - 0
src/views/company/downloadAppCount/index.vue

@@ -0,0 +1,213 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <el-form :model="queryParams" inline>
+
+        <el-form-item label="企微主体" prop="corpId">
+          <el-select v-model="queryParams.corpId" placeholder="企微主体" size="small" @change="updateCorpId()">
+            <el-option
+              v-for="dict in myQwCompanyList"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="所属部门" prop="deptId">
+          <treeselect style="width:220px" v-model="queryParams.deptId" :options="deptOptions" :show-count="true" placeholder="请选择所属部门" />
+        </el-form-item>
+
+        <el-form-item label="员工">
+          <el-input
+            v-model="queryParams.companyUserName"
+            placeholder="输入员工姓名"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="企微客服">
+          <el-input
+            v-model="queryParams.qwUserName"
+            placeholder="输入企微客服"
+            clearable
+          />
+        </el-form-item>
+
+        <el-form-item label="统计日期">
+          <el-date-picker
+            v-model="queryParams.statDate"
+            type="date"
+            placeholder="选择日期"
+            value-format="yyyy-MM-dd"
+          />
+        </el-form-item>
+
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">查询</el-button>
+          <el-button @click="handleReset">重置</el-button>
+          <el-button
+            type="warning"
+            plain
+            icon="el-icon-download"
+            :loading="exportLoading"
+            @click="handleExport"
+          >导出</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <el-table :data="tableData" border v-loading="loading">
+      <el-table-column prop="corpName" label="企微客服" align="center" />
+      <el-table-column
+        label="部门"
+        align="center"
+        :show-overflow-tooltip="true"
+        :formatter="formatDeptName"
+      />
+
+      <el-table-column prop="nickName" label="员工" align="center" />
+      <el-table-column prop="externalTotal" label="总客户数" align="center" />
+      <el-table-column prop="totalCustomers" label="已注册客户数" align="center" />
+      <el-table-column prop="downloadCount" label="下载APP数" align="center" />
+      <el-table-column prop="notDownloadCount" label="未下载APP数" align="center" />
+      <el-table-column prop="downloadRate" label="下载率" align="center">
+        <template slot-scope="scope">
+          {{ (scope.row.downloadRate * 100).toFixed(2) }}%
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </div>
+</template>
+
+<script>
+import {getSalesAppDownloadStats, exportSalesAppDownloadStats} from '../../../api/qw/externalContact';
+import {listDept, treeselect,listDeptExcludeChild} from "@/api/company/companyDept";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import {getMyQwCompanyList} from "@/api/qw/user";
+export default {
+  name: 'AppDownloadStatistics',
+  components: {Treeselect},
+  data() {
+    return {
+      queryParams: {
+        corpId: null,
+        pageNum: 1,
+        pageSize: 10,
+        departmentName: "",
+        companyUserName: "",
+        qwUserName: "",
+        myQwCompanyList: [],
+        statDate: this.formatDate(new Date())
+      },
+      tableData: [],
+      total: 0,
+      loading: false,
+      deptOptions: undefined,
+      deptList:[],
+      deptMap: {},
+      // 导出遮罩层
+      exportLoading: false,
+    }
+  },
+  watch: {
+    // 根据名称筛选部门树
+    deptName(val) {
+      this.$refs.tree.filter(val);
+    },
+  },
+  created() {
+    this.getList();
+    this.getTreeselect();
+    getMyQwCompanyList().then(response => {
+      this.myQwCompanyList = response.data;
+      if (this.myQwCompanyList != null) {
+        this.queryParams.corpId = this.myQwCompanyList[0].dictValue;
+        this.getList();
+      }
+    });
+  },
+  methods: {
+    // 格式化日期为 YYYY-MM-DD
+    formatDate(date) {
+      const year = date.getFullYear()
+      const month = String(date.getMonth() + 1).padStart(2, '0')
+      const day = String(date.getDate()).padStart(2, '0')
+      return `${year}-${month}-${day}`
+    },
+
+    // 获取统计数据 - 使用假数据
+    getList() {
+      this.loading = true;
+      getSalesAppDownloadStats(this.queryParams).then(response => {
+        this.tableData = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    /** 查询部门下拉树结构 */
+    getTreeselect() {
+      treeselect().then((response) => {
+        this.deptOptions = response.data;
+        this.deptMap = this.flattenDeptTree(this.deptOptions);
+      });
+    },
+    // 查询处理
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+
+    // 重置查询
+    handleReset() {
+      this.queryParams = {
+        pageNum: 1,
+        pageSize: 20,
+        departmentName: undefined,
+        employeeName: undefined,
+        statDate: this.formatDate(new Date())
+      }
+      this.getList()
+    },
+    flattenDeptTree(tree, map = {}) {
+      tree.forEach(node => {
+        map[node.id] = node.label
+        if (node.children) {
+          this.flattenDeptTree(node.children, map)
+        }
+      })
+      console.log("部门map",map)
+      return map
+    },
+    formatDeptName(row) {
+      const id = Number(row.deptId)
+      console.log("deptId",id)
+      return this.deptMap[id] || ''
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有销售统计项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportSalesAppDownloadStats(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
+    }
+
+  }
+}
+</script>

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

@@ -20,6 +20,12 @@
                         :picker-options="pickerOptions"
                         @change="change"></el-date-picker>
       </el-form-item>
+      <el-form-item label="搜索类型" prop="searchType">
+        <el-select v-model="queryParams.searchType" size="small" clearable placeholder="请选择搜索类型">
+          <el-option label="个人" value="1" />
+          <el-option label="部门" value="2" v-hasPermi="['qw:watchLogStatistics:dept']" />
+        </el-select>
+      </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -200,6 +206,7 @@ export default {
         eTime:null,
         scheduleStartTime: null,
         scheduleEndTime: null,
+        searchType: "1",
       },
       // 表单参数
       form: {},
@@ -390,6 +397,7 @@ export default {
 
       this.queryParams.scheduleStartTime = null;
       this.queryParams.scheduleEndTime = null;
+      this.queryParams.searchType = "1";
       this.handleQuery();
     },
     // 多选框选中数据

+ 54 - 17
src/views/qw/contactWay/index.vue

@@ -150,8 +150,13 @@
             <el-table-column label="state参数" align="center" prop="state" />
             <el-table-column label="用户" align="center" prop="userIds" width="200px">
               <template slot-scope="scope">
-                <div v-for="i in JSON.parse(scope.row.userIds)" :key="i" style="display: inline;">
-                <el-tag type="success" v-for="ii in companyUserList" :key="ii.id" style="margin: 3px;" v-if="ii.qwUserId==i">{{ii.qwUserName}}</el-tag>
+                <div>
+                  <el-tag type="success" v-for="(user, idx) in getMatchedUsers(scope.row.userIds)" :key="user.id" style="margin: 3px;" v-if="idx < 3 || expandedUserRows[scope.$index]">
+                    {{ user.qwUserName }}
+                  </el-tag>
+                  <span v-if="getMatchedUsers(scope.row.userIds).length > 3" style="cursor: pointer; color: #409EFF; font-size: 12px; margin-left: 5px;" @click="toggleUserExpand(scope.$index)">
+                    {{ expandedUserRows[scope.$index] ? '收起' : '展开(' + (getMatchedUsers(scope.row.userIds).length - 3) + ')' }}
+                  </span>
                 </div>
               </template>
             </el-table-column>
@@ -299,7 +304,7 @@
               closable
               :disable-transitions="false"
               @close="handleCloseGroupUser(user)">
-              {{'【'+user.qwUserId +'】'+ user.qwUserName }}
+              {{user.qwUserName }}
             </el-tag>
           </div>
 
@@ -345,7 +350,7 @@
            <el-option
              v-for="dict in companyUserList"
              :key="dict.qwUserId"
-             :label="'【'+ dict.qwUserId +'】'+ dict.qwUserName"
+             :label="dict.qwUserName"
              :value="dict.qwUserId">
            </el-option>
          </el-select>
@@ -704,7 +709,7 @@
     </el-dialog>
 
     <el-dialog :title="listUser.title" :visible.sync="listUser.open" width="700px" append-to-body>
-      <qwUserList ref="QwUserList" @selectUserList="selectUserList"></qwUserList>
+      <qwUserListWay ref="qwUserListWay" @selectUserList="selectUserList" @removeUser="handleRemoveUser"></qwUserListWay>
     </el-dialog>
 
     </div>
@@ -727,10 +732,10 @@ import ImageUpload from '@/views/qw/material/ImageUpload.vue'
 import statisDetails from '@/views/qw/contactWay/statisDetails';
 import { getMyQwUserList,getMyQwCompanyList } from "@/api/qw/user";
 import {courseList, videoList} from "@/api/qw/sop";
-import qwUserList from "@/views/qw/user/qwUserList.vue";
+import qwUserListWay from "@/views/qw/user/qwUserListWay.vue";
 export default {
   name: "ContactWay",
-   components: {qwUserList, contactWayGroup,ImageUpload,statisDetails},
+   components: {qwUserListWay, contactWayGroup,ImageUpload,statisDetails},
   data() {
     return {
       // 遮罩层
@@ -868,6 +873,7 @@ export default {
       },
       // 是否发送欢迎语字典
       companyUserList:[],
+      expandedUserRows: {},
       // 表单校验
       rules: {
         name:[{required:true,message:"名称不能为空",trigger:"blur"}],
@@ -939,6 +945,13 @@ export default {
 
   },
   methods: {
+    getMatchedUsers(userIdsStr) {
+      const userIds = JSON.parse(userIdsStr || '[]');
+      return this.companyUserList.filter(u => userIds.includes(u.qwUserId));
+    },
+    toggleUserExpand(index) {
+      this.$set(this.expandedUserRows, index, !this.expandedUserRows[index]);
+    },
     updateCorpId(){
       setTimeout(() => {
         this.$refs.contactWayGroup.getDetails(this.queryParams.corpId);
@@ -1564,7 +1577,7 @@ export default {
     //选择企微员工时
     handleListUser(type, sendType, selectOne) {
       setTimeout(() => {
-        this.$refs.QwUserList.getDetails(this.queryParams.corpId, type, sendType, selectOne);
+        this.$refs.qwUserListWay.getDetails(this.queryParams.corpId, type, sendType, selectOne, this.userIds);
       }, 1);
 
       this.listUser.title = "选择企微员工"
@@ -1620,10 +1633,14 @@ export default {
     },
     // 全天在线模式 - 删除员工标签
     handleCloseGroupUser(user) {
-      const index = this.userSelectList.findIndex(t => t.id === user.id);
-      if (index !== -1) {
-        this.userSelectList.splice(index, 1);
-        this.userIds.splice(index, 1);
+      const userSelectIndex = this.userSelectList.findIndex(t => t.id === user.id);
+      if (userSelectIndex !== -1) {
+        this.userSelectList.splice(userSelectIndex, 1);
+      }
+
+      const userIdsIndex = this.userIds.indexOf(user.qwUserId);
+      if (userIdsIndex !== -1) {
+        this.userIds.splice(userIdsIndex, 1);
       }
 
       for (let i = this.userLimitJson.length - 1; i >= 0; i--) {
@@ -1632,12 +1649,28 @@ export default {
         }
       }
 
+      if (this.$refs.qwUserListWay) {
+        this.$refs.qwUserListWay.selectedUserIds = [...this.userIds];
+      }
+
+    },
+    // 从员工选择弹窗中移除已选中员工
+    handleRemoveUser(user) {
+      if (this.currentEditingTimeSlotIndex === -1) {
+        // 全天在线模式
+        this.handleCloseGroupUser(user);
+      } else {
+        // 自动上下线模式
+        this.handleCloseTimeSlotUser(this.currentEditingTimeSlotIndex, user);
+      }
     },
     // 自动上下线模式 - 打开员工选择弹窗
     handleListUserForTimeSlot(timeSlotIndex) {
       this.currentEditingTimeSlotIndex = timeSlotIndex;
+      const timeSlot = this.userTimeJson[timeSlotIndex];
+      const selectedUserIds = timeSlot.userIds || [];
       setTimeout(() => {
-        this.$refs.QwUserList.getDetails(this.queryParams.corpId, this.form.type, this.form.sendType, true);
+        this.$refs.qwUserListWay.getDetails(this.queryParams.corpId, this.form.type, this.form.sendType, true, selectedUserIds);
       }, 1);
       this.listUser.title = "选择企微员工"
       this.listUser.open = true;
@@ -1647,12 +1680,16 @@ export default {
       const timeSlot = this.userTimeJson[timeSlotIndex];
       const userListIndex = timeSlot.userList.findIndex(t => t.id === user.id);
       if (userListIndex !== -1) {
-        // 使用 splice 删除并强制更新
         timeSlot.userList.splice(userListIndex, 1);
-        timeSlot.userIds.splice(userListIndex, 1);
-        // 强制更新整个 userTimeJson 数组以触发响应式更新
-        this.$set(this.userTimeJson, timeSlotIndex, {...timeSlot});
       }
+
+      const userIdsIndex = timeSlot.userIds.indexOf(user.qwUserId);
+      if (userIdsIndex !== -1) {
+        timeSlot.userIds.splice(userIdsIndex, 1);
+      }
+
+      this.$set(this.userTimeJson, timeSlotIndex, {...timeSlot});
+      this.$refs.qwUserListWay.selectedUserIds = timeSlot.userIds || [];
     },
   }
 };

+ 1207 - 0
src/views/qw/groupVoiceMsg/customerGroupDetails.vue

@@ -0,0 +1,1207 @@
+<template>
+  <div class="customer-group-details-container">
+    <el-card class="main-card">
+      <div class="section-header">
+        <i class="el-icon-data-analysis section-icon"></i>
+        <h2 class="section-title">数据统计</h2>
+      </div>
+      <div class="statistics-container">
+        <el-row :gutter="24">
+          <el-col :span="8" v-for="item in firstRowStats" :key="item.label">
+            <el-card class="statistic-card">
+              <div class="statistic-content">
+                <div class="statistic-value">{{ item.count }}</div>
+                <div class="statistic-label">{{ item.label }}</div>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+        <el-row :gutter="24" style="margin-top: 24px">
+          <el-col :span="8" v-for="item in secondRowStats" :key="item.label">
+            <el-card class="statistic-card">
+              <div class="statistic-content">
+                <div class="statistic-value">{{ item.count }}</div>
+                <div class="statistic-label">{{ item.label }}</div>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+      </div>
+    </el-card>
+
+    <el-card class="main-card">
+      <div class="section-header">
+        <i class="el-icon-phone-outline section-icon"></i>
+        <h2 class="section-title">呼叫详情</h2>
+      </div>
+
+      <div class="search-container">
+        <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="120px">
+          <el-form-item label="成员姓名" prop="nickName">
+            <el-input
+              v-model="queryParams.nickName"
+              placeholder="请输入想查询的成员姓名"
+              clearable
+              size="small"
+              @keyup.enter.native="handleQuery"
+              prefix-icon="el-icon-search"
+            />
+          </el-form-item>
+          <el-form-item label="客户名称" prop="name">
+            <el-input
+              v-model="queryParams.name"
+              placeholder="请输入想查询的客户姓名"
+              clearable
+              size="small"
+              @keyup.enter.native="handleQuery"
+              prefix-icon="el-icon-user"
+            />
+          </el-form-item>
+          <el-form-item label="呼叫状态" prop="sendStatus">
+              <el-select v-model="queryParams.sendStatus" placeholder="请选择客户拨通状态">
+                <el-option v-for="item in groupMsgSendStatusOptions"
+                           :key="item.dictValue"
+                           :label="item.dictLabel"
+                           :value="item.dictValue">
+                </el-option>
+              </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+            <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+
+      <el-table v-loading="loading" :data="groupMsgUserDetailsList" class="data-table" stripe>
+        <el-table-column label="企微成员" align="center" prop="nickName" />
+        <el-table-column label="客户" align="center" prop="name" >
+          <template slot-scope="scope">
+            <div class="customer-info">
+              <el-image
+                v-if="scope.row.avatar"
+                class="customer-avatar"
+                :src="scope.row.avatar"
+                fit="cover"
+                @click="openImageViewer(scope.row.avatar)"
+              />
+              <span class="customer-name">{{scope.row.name}}</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="预呼叫时间" align="center" prop="sendTime" />
+        <el-table-column label="呼叫状态" align="center" prop="sendStatus" >
+          <template slot-scope="scope">
+            <dict-tag :options="groupMsgSendStatusOptions" :value="scope.row.sendStatus"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="会话Id" align="center" prop="sessionId" />
+        <el-table-column label="外呼开始" align="center">
+          <template slot-scope="scope">
+            {{ scope.row.startCallTime ? $moment(scope.row.startCallTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="外呼结束" align="center">
+          <template slot-scope="scope">
+            {{ scope.row.endCallTime ? $moment(scope.row.endCallTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="外呼时长" align="center" prop="durationMsStr" />
+        <el-table-column label="通话详情" align="center">
+          <template slot-scope="scope">
+            <el-tooltip :content="scope.row.sessionId ? '查看通话详情' : '无通话记录'" placement="top">
+              <el-button
+                :type="scope.row.sessionId ? 'primary' : 'info'"
+                size="mini"
+                :disabled="!scope.row.sessionId"
+                @click="scope.row.sessionId && handleViewCallDetails(scope.row.sessionId)"
+                v-hasPermi="['qw:groupVoiceMsgUser:details:queryBySessionId']"
+                :icon="scope.row.sessionId ? 'el-icon-view' : 'el-icon-close-notification'"
+                class="detail-btn"
+              >
+                {{ scope.row.sessionId ? '查看详情' : '无详情' }}
+              </el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-container">
+        <pagination
+          v-show="total>0"
+          :total="total"
+          :page.sync="queryParams.pageNum"
+          :limit.sync="queryParams.pageSize"
+          @pagination="getUserList"
+        />
+      </div>
+    </el-card>
+
+    <!-- 通话详情弹窗 -->
+    <el-dialog
+      title="通话详情"
+      :visible.sync="callDetailsDialogVisible"
+      width="60%"
+      class="detail-dialog"
+      append-to-body
+      :close-on-click-modal="false"
+    >
+      <div v-loading="callDetailsLoading">
+        <div v-if="callDetails">
+          <!-- 基本信息 -->
+          <el-card class="info-card">
+            <div slot="header" class="info-header">
+              <span class="info-title"><i class="el-icon-info"></i> 通话概况</span>
+            </div>
+            <div class="info-grid">
+              <div class="info-item session-id">
+                <div class="info-icon session-id-bg">
+                  <i class="el-icon-connection"></i>
+                </div>
+                <div class="info-content">
+                  <div class="info-label">会话ID</div>
+                  <div class="info-value session-id-value">
+                    {{ callDetails.sessionId || '-' }}
+                    <el-tooltip content="复制会话ID" placement="top">
+                      <el-button
+                        v-if="callDetails.sessionId"
+                        type="text"
+                        size="mini"
+                        icon="el-icon-document-copy"
+                        @click="copySessionId(callDetails.sessionId)"
+                        class="copy-btn"
+                      />
+                    </el-tooltip>
+                  </div>
+                </div>
+              </div>
+
+              <div class="info-item agent">
+                <div class="info-icon agent-bg">
+                  <i class="el-icon-service"></i>
+                </div>
+                <div class="info-content">
+                  <div class="info-label">客服</div>
+                  <div class="info-value">{{ callDetails.aiSopCallRoleName || '-' }}</div>
+                </div>
+              </div>
+
+              <div class="info-item customer">
+                <div class="info-icon customer-bg">
+                  <i class="el-icon-user"></i>
+                </div>
+                <div class="info-content">
+                  <div class="info-label">用户</div>
+                  <div class="info-value">{{ callDetails.qwUserNickName || '-' }}</div>
+                </div>
+              </div>
+
+              <el-tooltip
+                :content="getSatisfactionTooltip()"
+                placement="top"
+              >
+                <div class="info-item satisfaction">
+                  <div class="info-icon satisfaction-bg">
+                    <i class="el-icon-star-on"></i>
+                  </div>
+                  <div class="info-content">
+                    <div class="info-label">满意度</div>
+                    <div class="info-value satisfaction-stars">
+                      <span
+                        v-for="star in 5"
+                        :key="star"
+                        :class="['star', getStarClass(star)]"
+                      >
+                        <i :class="getStarIcon(star)"></i>
+                      </span>
+                    </div>
+                  </div>
+                </div>
+              </el-tooltip>
+            </div>
+          </el-card>
+
+          <!-- 聊天记录 -->
+          <el-card class="chat-card">
+            <div slot="header" class="info-header">
+              <span class="info-title"><i class="el-icon-chat-line-round"></i> 聊天记录</span>
+            </div>
+            <chat-record-viewer
+              :chat-data="callDetails.aiChatDetails"
+              :initial-display-count="4"
+              :agent-name="callDetails.aiSopCallRoleName"
+              :customer-name="callDetails.externalUserName"
+              class="chat-viewer"
+            />
+          </el-card>
+
+          <!-- 语音记录 -->
+          <el-card class="voice-card">
+            <div slot="header" class="info-header">
+              <span class="info-title"><i class="el-icon-microphone"></i> 语音记录</span>
+            </div>
+            <div class="voice-container">
+              <div v-if="callDetails.voiceText" class="voice-list">
+                <!-- 只显示前两条语音 -->
+                <div v-for="(voice, index) in getVoiceList(callDetails.voiceText).slice(0, 2)"
+                     :key="index"
+                     class="voice-item">
+                  <div class="voice-row">
+                    <div class="voice-info">
+                      <i class="el-icon-headset voice-icon"></i>
+                      <span class="voice-title">语音{{ getChineseNumber(index + 1) }}</span>
+                    </div>
+                    <div class="voice-player">
+                      <audio
+                          :src="voice"
+                          controls
+                          class="audio-player"
+                      ></audio>
+                    </div>
+                  </div>
+                </div>
+
+                <!-- 显示更多语音按钮(展开后隐藏) -->
+                <div v-if="getVoiceList(callDetails.voiceText).length > 2 && !showAllVoices" class="expand-voice">
+                  <el-button
+                      type="text"
+                      size="mini"
+                      @click="showAllVoices = true"
+                      class="expand-btn"
+                  >
+                    {{ `展开全部 (${getVoiceList(callDetails.voiceText).length - 2}条)` }}
+                  </el-button>
+                </div>
+
+                <!-- 展开后的额外语音 -->
+                <div v-if="showAllVoices">
+                  <div v-for="(voice, index) in getVoiceList(callDetails.voiceText).slice(2)"
+                       :key="index + 2"
+                       class="voice-item">
+                    <div class="voice-row">
+                      <div class="voice-info">
+                        <i class="el-icon-headset voice-icon"></i>
+                        <span class="voice-title">语音{{ getChineseNumber(index + 3) }}</span>
+                      </div>
+                      <div class="voice-player">
+                        <audio
+                            :src="voice"
+                            controls
+                            class="audio-player"
+                        ></audio>
+                      </div>
+                    </div>
+                  </div>
+
+                  <!-- 收起按钮只有在语音总数超过2条时才显示 -->
+                  <div class="expand-voice" v-if="getVoiceList(callDetails.voiceText).length > 2">
+                    <el-button
+                        type="text"
+                        size="mini"
+                        @click="showAllVoices = false"
+                        class="expand-btn"
+                    >
+                      收起
+                    </el-button>
+                  </div>
+                </div>
+              </div>
+              <div v-else class="no-voice-data">
+                <i class="el-icon-microphone"></i>
+                <p>暂无语音记录</p>
+              </div>
+            </div>
+          </el-card>
+
+        </div>
+
+        <div v-else-if="!callDetailsLoading" class="no-data">
+          <i class="el-icon-warning-outline"></i>
+          <p>暂无呼叫详情数据</p>
+        </div>
+      </div>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="callDetailsDialogVisible = false">关闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { countGroupVoiceMsgUser, CountGroupVoiceMsgUserDetails} from '@/api/qw/groupVoiceMsg'
+import { getBySessionIdDetails } from '@/api/qw/groupVoiceMsgUserDetails'
+import ChatRecordViewer from '@/components/chat/ChatRecordViewer.vue'
+
+export default {
+  name: "Customer-group-details",
+  components:{
+    ChatRecordViewer
+  },
+
+  props:{
+    rowDetailFrom:{},
+  },
+
+  watch:{
+    rowDetailFrom:{
+      handler(newVal){
+        // 当formData变化时重新查询
+        this.getUserList(newVal);
+        this.getcountGroupVoiceMsgUser(newVal)
+      },
+      deep: true
+    }
+  },
+  data() {
+    return {
+      //详情记录id
+      groupMsgId: null,
+      //数据统计
+      countGroupVoiceMsgUserList: [],
+
+      //成员详情
+      groupMsgUserDetailsList:[],
+
+      //客户拨通状态
+      groupMsgSendStatusOptions:[],
+
+      //放大图片
+      dialogImageUrl:null,
+      dialogVisible:false,
+
+      // 呼叫详情弹窗
+      callDetailsDialogVisible: false,
+      callDetails: null,
+      callDetailsLoading: false,
+      showAllVoices: false,
+
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+
+
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        qwMsgId:null,
+        msgId:null,
+        nickName:null,
+        name:null,
+        status:null,
+        sendStatus:null,
+        chatType: 1 ,
+
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+
+      },
+
+    };
+  },
+  computed: {
+    firstRowStats() {
+      const order = ['待呼叫', '已呼叫', '呼叫失败'];
+      return order.map(label => {
+        const item = this.countGroupVoiceMsgUserList.find(stat => stat.label === label);
+        return item ? item : { label, count: 0 };
+      });
+    },
+    secondRowStats() {
+      const order = ['不是好友呼叫失败', '已作废', '系统处理异常'];
+      return order.map(label => {
+        const item = this.countGroupVoiceMsgUserList.find(stat => stat.label === label);
+        return item ? item : { label, count: 0 };
+      });
+    }
+  },
+  created() {
+    this.getUserList(this.rowDetailFrom)
+    this.getcountGroupVoiceMsgUser(this.rowDetailFrom)
+    //客户接收状态
+    this.getDicts("sys_qw_ai_call_sendStatus").then(response => {
+      this.groupMsgSendStatusOptions = response.data;
+    });
+
+
+  },
+
+
+methods: {
+  //获取成员详细
+  getUserList(val){
+    this.queryParams.msgId = val.id || this.rowDetailFrom.msgId;
+    // 检查是否为 null、undefined 或空字符串
+    if (this.queryParams.msgId) {
+      this.groupMsgId = this.queryParams.msgId;
+    }else{
+      this.queryParams.msgId = this.groupMsgId;
+    }
+    this.loading=false;
+    CountGroupVoiceMsgUserDetails(this.queryParams).then(res=>{
+          this.groupMsgUserDetailsList=res.rows
+          this.total = res.total;
+          this.loading = false;
+    })
+  },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getUserList(this.rowDetailFrom);
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+
+    openImageViewer(url) {
+      // 打开大图预览对话框
+      this.dialogImageUrl=url
+      this.dialogVisible = true;
+    },
+    //数据详情
+    getcountGroupVoiceMsgUser(val){
+      countGroupVoiceMsgUser(val.msgid).then(res=>{
+        this.countGroupVoiceMsgUserList=Object.keys(res.data).map(key=>{
+          return {label: key,count:res.data[key]}
+        })
+      })
+    },
+
+    /** 查看呼叫详情 */
+    handleViewCallDetails(sessionId) {
+      this.callDetailsDialogVisible = true;
+      this.callDetailsLoading = true;
+      this.callDetails = null;
+      this.showAllVoices = false; // 重置语音展开状态
+
+      getBySessionIdDetails(sessionId).then(response => {
+        this.callDetails = response.data;
+        this.callDetailsLoading = false;
+
+        // 自动展开所有语音记录(如果只有2条或更少)
+        if (this.callDetails.voiceText) {
+          const voiceCount = this.getVoiceList(this.callDetails.voiceText).length;
+          if (voiceCount <= 2) {
+            this.showAllVoices = true;
+          }
+        }
+      }).catch(() => {
+        this.callDetailsLoading = false;
+      });
+    },
+
+    /** 获取语音记录列表 */
+    getVoiceList(voiceText) {
+      if (!voiceText) return [];
+      // 替换中文逗号为英文逗号,然后分割
+      return voiceText.split(',')
+        .map(voice => voice.trim())
+        .filter(voice => voice.length > 0);
+    },
+
+    /** 获取中文数字 */
+    getChineseNumber(num) {
+      const chineseNumbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
+      if (num >= 1 && num <= 10) {
+        return chineseNumbers[num - 1];
+      }
+      return num;
+    },
+
+    /** 获取星星样式类 */
+    getStarClass(starIndex) {
+      const satisfaction = this.callDetails?.satisfaction || 0;
+
+      if (satisfaction === 1) {
+        // 1级满意度:第1个星星显示灰色,其余为空心
+        return starIndex <= 1 ? 'star-gray' : 'star-empty';
+      } else if (satisfaction === 2) {
+        // 2级满意度:前3个星星显示橙色,其余为空心
+        return starIndex <= 3 ? 'star-orange' : 'star-empty';
+      } else if (satisfaction === 3) {
+        // 3级满意度:5个星星都显示红色
+        return 'star-red';
+      } else {
+        // 无满意度:全部为空心
+        return 'star-empty';
+      }
+    },
+
+    /** 获取星星图标 */
+    getStarIcon(starIndex) {
+      const satisfaction = this.callDetails?.satisfaction || 0;
+
+      if (satisfaction === 1) {
+        // 1级满意度:第1个星星实心灰色,其余空心
+        return starIndex <= 1 ? 'el-icon-star-on' : 'el-icon-star-off';
+      } else if (satisfaction === 2) {
+        // 2级满意度:前3个星星实心橙色,其余空心
+        return starIndex <= 3 ? 'el-icon-star-on' : 'el-icon-star-off';
+      } else if (satisfaction === 3) {
+        // 3级满意度:5个星星都实心红色
+        return 'el-icon-star-on';
+      } else {
+        // 无满意度:全部空心
+        return 'el-icon-star-off';
+      }
+    },
+
+    /** 获取满意度提示文本 */
+    getSatisfactionTooltip() {
+      const satisfaction = this.callDetails?.satisfaction;
+      if (satisfaction === 1) return '满意度:低';
+      if (satisfaction === 2) return '满意度:中等';
+      if (satisfaction === 3) return '满意度:高';
+      return '暂无满意度评价';
+    },
+
+    /** 复制会话ID */
+    copySessionId(sessionId) {
+      if (!sessionId) return;
+
+      // 创建临时textarea元素进行复制
+      const textarea = document.createElement('textarea');
+      textarea.value = sessionId;
+      textarea.style.position = 'fixed';
+      textarea.style.opacity = '0';
+      document.body.appendChild(textarea);
+
+      try {
+        textarea.select();
+        const successful = document.execCommand('copy');
+        if (successful) {
+          this.msgSuccess('会话ID已复制到剪贴板');
+        } else {
+          this.msgError('复制失败,请手动复制');
+        }
+      } catch (err) {
+        this.msgError('复制失败: ' + err.message);
+      } finally {
+        document.body.removeChild(textarea);
+      }
+    },
+  }
+};
+</script>
+<style scoped>
+.customer-group-details-container {
+  padding: 16px;
+  background-color: #f5f7fa;
+  min-height: 100vh;
+  font-family: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+  overflow-x: hidden;
+  box-sizing: border-box;
+}
+
+.main-card {
+  margin-bottom: 16px;
+  border: none;
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
+  overflow: hidden;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 20px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.section-icon {
+  font-size: 20px;
+  margin-right: 10px;
+  color: #409EFF;
+}
+
+.section-title {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+}
+
+/* 统计卡片 */
+.statistics-container {
+  margin-top: 16px;
+  width: 100%;
+  overflow: hidden;
+}
+
+.statistic-card {
+  border: none;
+  background: linear-gradient(135deg, #ffffff, #fdfdfd);
+  border-radius: 10px;
+  padding: 16px;
+  height: 100px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: all 0.3s ease;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
+  overflow: hidden;
+  box-sizing: border-box;
+}
+
+.statistic-card:hover {
+  transform: translateY(-3px);
+  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
+}
+
+.statistic-content {
+  text-align: center;
+  width: 100%;
+  overflow: hidden;
+}
+
+.statistic-value {
+  font-size: 28px;
+  font-weight: 700;
+  color: #409EFF;
+  margin-bottom: 4px;
+  line-height: 1.2;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.statistic-label {
+  font-size: 14px;
+  color: #606266;
+  font-weight: 500;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+/* 搜索表单 */
+.search-container {
+  background: #fff;
+  padding: 20px;
+  border-radius: 8px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+  overflow: hidden;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+/* 数据表格 */
+.data-table {
+  width: 100%;
+  background: #fff;
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+  margin-bottom: 8px;
+}
+
+.customer-info {
+  display: flex;
+  align-items: center;
+}
+
+.customer-avatar {
+  width: 30px;
+  height: 30px;
+  border-radius: 50%;
+  margin-right: 8px;
+  cursor: pointer;
+  border: 1px solid #e4e7f1;
+}
+
+.customer-name {
+  vertical-align: middle;
+}
+
+.detail-btn {
+  transition: all 0.2s ease;
+  border-radius: 6px;
+  padding: 6px 12px;
+}
+
+.detail-btn:hover {
+  transform: translateY(-1px);
+  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+}
+
+.pagination-container {
+  margin-top: 10px;
+  text-align: right;
+  padding: 8px 0;
+  background: transparent;
+}
+
+/* 通话详情弹窗 */
+.detail-dialog ::v-deep .el-dialog {
+  border-radius: 16px;
+  overflow: hidden;
+  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
+  margin: 0;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  max-height: 85vh;
+  overflow-y: auto;
+}
+
+.detail-dialog ::v-deep .el-dialog__header {
+  background: linear-gradient(135deg, #409EFF 0%, #4a9eff 100%);
+  padding: 12px 16px;
+  margin: 0;
+}
+
+.detail-dialog ::v-deep .el-dialog__title {
+  color: white;
+  font-weight: 600;
+  font-size: 16px;
+}
+
+.detail-dialog ::v-deep .el-dialog__headerbtn {
+  top: 12px;
+  right: 16px;
+}
+
+.detail-dialog ::v-deep .el-dialog__headerbtn .el-dialog__close {
+  color: white;
+  font-size: 16px;
+}
+
+.detail-dialog ::v-deep .el-dialog__body {
+  padding: 12px;
+  background: #f9fafc;
+  max-height: 65vh;
+  overflow-y: auto;
+}
+
+.detail-dialog ::v-deep .el-dialog__footer {
+  padding: 8px 12px;
+  background: #f9fafc;
+  border-top: 1px solid #e6e9f0;
+}
+
+/* 信息卡片 */
+.info-header {
+  padding: 8px 12px !important;
+  background: #f8f9fc;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.info-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+  display: flex;
+  align-items: center;
+}
+
+.info-title i {
+  margin-right: 6px;
+  color: #409EFF;
+}
+
+.info-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 10px;
+  padding: 8px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  background: white;
+  border-radius: 8px;
+  border: 1px solid #e6ebf5;
+  transition: all 0.3s ease;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.03);
+}
+
+.info-item:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+  border-color: #409EFF;
+}
+
+.info-icon {
+  width: 32px;
+  height: 32px;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 8px;
+  flex-shrink: 0;
+  font-size: 14px;
+  color: white;
+}
+
+.session-id-bg {
+  background: linear-gradient(135deg, #409EFF 0%, #5dade2 100%);
+}
+
+.agent-bg {
+  background: linear-gradient(135deg, #1abc9c 0%, #48c9b0 100%);
+}
+
+.customer-bg {
+  background: linear-gradient(135deg, #e74c3c 0%, #ec7063 100%);
+}
+
+.satisfaction-bg {
+  background: linear-gradient(135deg, #f39c12 0%, #f5b041 100%);
+}
+
+.info-content {
+  flex: 1;
+}
+
+.info-label {
+  font-size: 12px;
+  font-weight: 600;
+  color: #606266;
+  margin-bottom: 2px;
+  text-transform: uppercase;
+  letter-spacing: 0.5px;
+}
+
+.info-value {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+  word-break: break-all;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.session-id-value {
+  font-family: 'Courier New', Consolas, monospace;
+  color: #1890ff;
+  font-size: 14px;
+}
+
+.copy-btn {
+  margin-left: 6px;
+  color: #909399;
+  font-size: 12px;
+}
+
+.copy-btn:hover {
+  color: #409EFF;
+}
+
+/* 聊天记录卡片 */
+.chat-card {
+  border: none;
+  border-radius: 12px;
+  overflow: hidden;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
+  margin-bottom: 12px;
+  background: white;
+}
+
+.chat-viewer {
+  margin-top: -10px;
+}
+
+.chat-viewer ::v-deep .chat-messages {
+  padding: 8px 12px;
+}
+
+.chat-viewer ::v-deep .message {
+  margin-bottom: 10px !important;
+  padding: 8px 12px !important;
+  font-size: 13px;
+  line-height: 1.4;
+  border-radius: 8px;
+  max-width: 80%;
+}
+
+.chat-viewer ::v-deep .message-agent {
+  background: #ecf5ff;
+  border: 1px solid #d9ecff;
+  align-self: flex-start;
+}
+
+.chat-viewer ::v-deep .message-customer {
+  background: #f0f9ff;
+  border: 1px solid #c6e2ff;
+  align-self: flex-end;
+}
+
+/* 语音记录卡片 */
+.voice-card {
+  border: none;
+  border-radius: 12px;
+  overflow: hidden;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
+  background: white;
+  margin-bottom: 0;
+}
+
+.voice-container {
+  padding: 8px;
+}
+
+.voice-list {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.voice-item {
+  background: white;
+  border-radius: 8px;
+  border: 1px solid #e1e8ef;
+  padding: 10px;
+  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.03);
+}
+
+.voice-item:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.1);
+  border-color: rgba(64, 158, 255, 0.3);
+}
+
+.voice-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+}
+
+.voice-info {
+  display: flex;
+  align-items: center;
+  flex-shrink: 0;
+}
+
+.voice-icon {
+  width: 26px;
+  height: 26px;
+  background: linear-gradient(135deg, #409EFF 0%, #64B5FF 100%);
+  color: white;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 8px;
+  font-size: 12px;
+}
+
+.voice-title {
+  font-size: 13px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.voice-player {
+  flex: 1;
+  min-width: 0;
+}
+
+.audio-player {
+  width: 100%;
+  height: 32px;
+  border-radius: 16px;
+  background: #f0f9ff;
+  border: 1px solid #d9ecff;
+}
+
+.no-voice-data {
+  text-align: center;
+  padding: 30px 16px;
+  background: #f8fafc;
+  border-radius: 8px;
+  color: #909399;
+}
+
+.no-voice-data i {
+  font-size: 32px;
+  margin-bottom: 10px;
+  display: block;
+  color: #c0c4cc;
+}
+
+.no-voice-data p {
+  font-size: 13px;
+  color: #909399;
+  margin: 0;
+}
+
+.no-data {
+  text-align: center;
+  padding: 24px 12px;
+}
+
+.no-data i {
+  font-size: 28px;
+  margin-bottom: 10px;
+  color: #c0c4cc;
+}
+
+.no-data p {
+  font-size: 13px;
+  color: #909399;
+  margin: 0;
+}
+
+/* 满意度星星样式 */
+.satisfaction-stars {
+  display: flex;
+  align-items: center;
+  gap: 2px;
+}
+
+.star {
+  font-size: 14px;
+  transition: all 0.3s ease;
+}
+
+.star i {
+  display: block;
+}
+
+/* 空心灰色星星(未点亮的星星) */
+.star-empty i {
+  color: #e4e7f1;
+}
+
+/* 灰色星星(1级满意度) */
+.star-gray i {
+  color: #909399;
+}
+
+/* 橙色星星(2级满意度) */
+.star-orange i {
+  color: #e6a23c;
+}
+
+/* 红色星星(3级满意度) */
+.star-red i {
+  color: #f56c6c;
+}
+
+/* 展开按钮 */
+.expand-voice {
+  text-align: center;
+  padding: 10px 0;
+}
+
+.expand-btn {
+  padding: 4px 10px !important;
+  font-size: 12px;
+  border-radius: 16px;
+  color: #409EFF;
+  border: 1px solid #d9ecff;
+  transition: all 0.3s ease;
+}
+
+.expand-btn:hover {
+  background-color: #ecf5ff;
+  color: #66b1ff;
+  border-color: #c6e2ff;
+}
+
+/* 底部按钮 */
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  padding: 12px 16px;
+}
+
+.dialog-footer .el-button {
+  padding: 6px 16px;
+  border-radius: 6px;
+  font-weight: 500;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .customer-group-details-container {
+    padding: 12px;
+  }
+
+  .info-grid {
+    grid-template-columns: 1fr;
+    gap: 10px;
+  }
+
+  .voice-row {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 10px;
+  }
+
+  .voice-player {
+    width: 100%;
+  }
+
+  .info-item {
+    padding: 10px;
+  }
+
+  .info-icon {
+    width: 32px;
+    height: 32px;
+    font-size: 14px;
+  }
+
+  .statistic-value {
+    font-size: 22px;
+  }
+
+  .statistic-label {
+    font-size: 12px;
+  }
+
+  .detail-dialog ::v-deep .el-dialog {
+    width: 90% !important;
+    margin: 0;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+  }
+
+  .chat-viewer ::v-deep .message {
+    max-width: 90%;
+    font-size: 12px;
+    padding: 6px 10px !important;
+  }
+
+  .detail-dialog ::v-deep .el-dialog__body {
+    max-height: 60vh;
+    padding: 12px;
+  }
+}
+</style>

+ 577 - 0
src/views/qw/groupVoiceMsg/detailsRecord/index.vue

@@ -0,0 +1,577 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never" class="search-card">
+      <div slot="header" class="clearfix">
+        <span class="card-title"><i class="el-icon-search"></i> 搜索条件</span>
+        <el-button
+          style="float: right; padding: 3px 0"
+          type="text"
+          @click="showSearch = !showSearch">
+          {{ showSearch ? '收起' : '展开' }}
+        </el-button>
+      </div>
+      <el-form v-show="showSearch" :model="queryParams" ref="queryForm" :inline="true" label-width="90px" class="compact-form">
+      <el-form-item label="会话ID" prop="sessionId">
+        <el-input
+          v-model="queryParams.sessionId"
+          placeholder="请输入会话ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+          style="width: 160px"
+        />
+      </el-form-item>
+
+      <el-form-item label="满意度" prop="satisfaction">
+        <el-select
+          v-model="queryParams.satisfaction"
+          placeholder="全部满意度"
+          clearable
+          size="small"
+          style="width: 120px"
+        >
+          <el-option
+            v-for="item in satisfactionOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="用户昵称" prop="qwUserNickName">
+        <el-input
+          v-model="queryParams.qwUserNickName"
+          placeholder="请输入用户昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+          style="width: 160px"
+        />
+      </el-form-item>
+
+      <el-form-item label="外呼角色" prop="aiSopCallRoleName">
+        <el-input
+          v-model="queryParams.aiSopCallRoleName"
+          placeholder="请输入外呼角色名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+          style="width: 160px"
+        />
+      </el-form-item>
+
+      <el-form-item label="外部联系人" prop="externalUserName">
+        <el-input
+          v-model="queryParams.externalUserName"
+          placeholder="请输入外部联系人名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+          style="width: 160px"
+        />
+      </el-form-item>
+
+      <el-form-item class="form-actions">
+        <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
+      </el-form-item>
+      </el-form>
+    </el-card>
+
+    <el-row :gutter="10" class="mb8 operation-row">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['company:details:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['company:details:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['company:details:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['company:details:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-card shadow="never" class="table-card">
+      <div slot="header" class="clearfix">
+        <span class="card-title"><i class="el-icon-document"></i> 数据列表</span>
+        <span class="total-text">共 {{ total }} 条记录</span>
+      </div>
+      <el-table
+        v-loading="loading"
+        :data="detailsList"
+        @selection-change="handleSelectionChange"
+        stripe
+        style="width: 100%"
+        class="custom-table"
+      >
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="主键ID" align="center" prop="id" />
+      <el-table-column label="会话id" align="center" prop="sessionId" />
+      <el-table-column label="原语音" align="center" prop="voiceText" />
+      <el-table-column label="聊天详情" align="center" prop="aiChatDetails" />
+      <el-table-column label="满意度" align="center" width="80">
+        <template slot-scope="scope">
+          <el-tag
+            :type="scope.row.satisfaction === 3 ? 'success' : scope.row.satisfaction === 2 ? 'warning' : 'danger'"
+            size="small"
+          >
+            {{ scope.row.satisfaction === 1 ? '低' : scope.row.satisfaction === 2 ? '中' : scope.row.satisfaction === 3 ? '高' : '-' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="企微用户昵称" align="center" prop="qwUserNickName" />
+      <el-table-column label="外呼角色名称" align="center" prop="aiSopCallRoleName" />
+      <el-table-column label="外部联系人username" align="center" prop="externalUserName" />
+      <el-table-column label="token用量" align="center" prop="totalToken" />
+      <el-table-column label="记录总用量" align="center" prop="chatTotalToken" />
+      <el-table-column label="记录输入量" align="center" prop="chatPromptToken" />
+      <el-table-column label="记录输出用量" align="center" prop="chatCompletionToken" />
+      <el-table-column label="操作" align="center" width="120" fixed="right">
+        <template slot-scope="scope">
+          <el-tooltip content="修改" placement="top">
+            <el-button
+              size="mini"
+              type="primary"
+              icon="el-icon-edit"
+              circle
+              @click="handleUpdate(scope.row)"
+              v-hasPermi="['company:details:edit']"
+            />
+          </el-tooltip>
+          <el-tooltip content="删除" placement="top">
+            <el-button
+              size="mini"
+              type="danger"
+              icon="el-icon-delete"
+              circle
+              @click="handleDelete(scope.row)"
+              v-hasPermi="['company:details:remove']"
+            />
+          </el-tooltip>
+        </template>
+      </el-table-column>
+      </el-table>
+    </el-card>
+
+    <el-pagination
+      class="pagination-container"
+      :current-page="queryParams.pageNum"
+      :page-size="queryParams.pageSize"
+      :page-sizes="[10, 20, 50, 100]"
+      :total="total"
+      layout="total, sizes, prev, pager, next, jumper"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+
+    <!-- 添加或修改AI外呼客服呼叫详情对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="会话id" prop="sessionId">
+          <el-input v-model="form.sessionId" placeholder="请输入会话id" />
+        </el-form-item>
+        <el-form-item label="原语音" prop="voiceText">
+          <el-input v-model="form.voiceText" placeholder="请输入原语音" />
+        </el-form-item>
+        <el-form-item label="原聊天详情" prop="chatDetails">
+          <el-input v-model="form.chatDetails" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+        <el-form-item label="ai质检语音" prop="aiVoiceText">
+          <el-input v-model="form.aiVoiceText" placeholder="请输入ai质检语音" />
+        </el-form-item>
+        <el-form-item label="ai质检聊天详情" prop="aiChatDetails">
+          <el-input v-model="form.aiChatDetails" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+        <el-form-item label="满意度" prop="satisfaction">
+          <el-select v-model="form.satisfaction" placeholder="请选择满意度" style="width: 100%">
+            <el-option
+              v-for="item in satisfactionOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="ID" prop="companyId">
+          <el-input v-model="form.companyId" placeholder="请输入ID" />
+        </el-form-item>
+        <el-form-item label="企业名" prop="companyName">
+          <el-input v-model="form.companyName" placeholder="请输入企业名" />
+        </el-form-item>
+        <el-form-item label="企微用户id" prop="qwUserId">
+          <el-input v-model="form.qwUserId" placeholder="请输入企微用户id" />
+        </el-form-item>
+        <el-form-item label="用户昵称" prop="qwUserNickName">
+          <el-input v-model="form.qwUserNickName" placeholder="请输入用户昵称" />
+        </el-form-item>
+        <el-form-item label="客服id" prop="aiSopCallRoleId">
+          <el-input v-model="form.aiSopCallRoleId" placeholder="请输入客服id" />
+        </el-form-item>
+        <el-form-item label="外呼角色名称" prop="aiSopCallRoleName">
+          <el-input v-model="form.aiSopCallRoleName" placeholder="请输入外呼角色名称" />
+        </el-form-item>
+        <el-form-item label="外部联系人id" prop="externalUserId">
+          <el-input v-model="form.externalUserId" placeholder="请输入外部联系人id" />
+        </el-form-item>
+        <el-form-item label="外部联系人username" prop="externalUserName">
+          <el-input v-model="form.externalUserName" placeholder="请输入外部联系人username" />
+        </el-form-item>
+        <el-form-item label="token用量" prop="totalToken">
+          <el-input v-model="form.totalToken" placeholder="请输入token用量" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+        <el-form-item label="语音token总用量" prop="voiceTotalToken">
+          <el-input v-model="form.voiceTotalToken" placeholder="请输入语音token总用量" />
+        </el-form-item>
+        <el-form-item label="语音输入量" prop="voicePromptToken">
+          <el-input v-model="form.voicePromptToken" placeholder="请输入语音输入量" />
+        </el-form-item>
+        <el-form-item label="语音输出量" prop="voiceCompletionToken">
+          <el-input v-model="form.voiceCompletionToken" placeholder="请输入语音输出量" />
+        </el-form-item>
+        <el-form-item label="记录总用量" prop="chatTotalToken">
+          <el-input v-model="form.chatTotalToken" placeholder="请输入记录总用量" />
+        </el-form-item>
+        <el-form-item label="记录输入量" prop="chatPromptToken">
+          <el-input v-model="form.chatPromptToken" placeholder="请输入记录输入量" />
+        </el-form-item>
+        <el-form-item label="记录输出用量" prop="chatCompletionToken">
+          <el-input v-model="form.chatCompletionToken" placeholder="请输入记录输出用量" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listDetails, getDetails, delDetails, addDetails, updateDetails, exportDetails } from "@/api/qw/groupVoiceMsgUserDetails";
+
+export default {
+  name: "detailsRecord",
+  data() {
+    return {
+      // 满意度选项
+      satisfactionOptions: [
+        { value: '1', label: '低' },
+        { value: '2', label: '中' },
+        { value: '3', label: '高' }
+      ],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // AI外呼客服呼叫详情表格数据
+      detailsList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        sessionId: null,
+        voiceText: null,
+        chatDetails: null,
+        aiVoiceText: null,
+        aiChatDetails: null,
+        satisfaction: undefined,
+        companyId: null,
+        companyName: null,
+        qwUserId: null,
+        qwUserNickName: null,
+        aiSopCallRoleId: null,
+        aiSopCallRoleName: null,
+        externalUserId: null,
+        externalUserName: null,
+        totalToken: null,
+        voiceTotalToken: null,
+        voicePromptToken: null,
+        voiceCompletionToken: null,
+        chatTotalToken: null,
+        chatPromptToken: null,
+        chatCompletionToken: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        qwUserNickName: [
+          { required: true, message: "用户昵称不能为空", trigger: "blur" }
+        ],
+      }
+    };
+  },
+  created() {
+    // 从路由参数中获取sessionId
+    if (this.$route.query.sessionId) {
+      this.queryParams.sessionId = this.$route.query.sessionId;
+    }
+    // 清理可能存在的无效参数
+    if (this.$route.query.aiSopCallRoleId === '0') {
+      delete this.queryParams.aiSopCallRoleId;
+    }
+    this.getList();
+  },
+  methods: {
+    /** 查询AI外呼客服呼叫详情列表 */
+    getList() {
+      this.loading = true;
+      listDetails(this.queryParams).then(response => {
+        this.detailsList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 分页大小变化
+    handleSizeChange(val) {
+      this.queryParams.pageSize = val;
+      this.getList();
+    },
+    // 当前页码变化
+    handleCurrentChange(val) {
+      this.queryParams.pageNum = val;
+      this.getList();
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        sessionId: null,
+        voiceText: null,
+        chatDetails: null,
+        aiVoiceText: null,
+        aiChatDetails: null,
+        satisfaction: undefined,
+        companyId: null,
+        companyName: null,
+        qwUserId: null,
+        qwUserNickName: null,
+        aiSopCallRoleId: null,
+        aiSopCallRoleName: null,
+        externalUserId: null,
+        externalUserName: null,
+        totalToken: null,
+        remark: null,
+        voiceTotalToken: null,
+        createTime: null,
+        voicePromptToken: null,
+        voiceCompletionToken: null,
+        chatTotalToken: null,
+        chatPromptToken: null,
+        chatCompletionToken: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加AI外呼客服呼叫详情";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getDetails(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改AI外呼客服呼叫详情";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateDetails(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addDetails(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除AI外呼客服呼叫详情编号为"' + ids + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delDetails(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有AI外呼客服呼叫详情数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportDetails(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+}
+
+.search-card {
+  margin-bottom: 20px;
+  border-radius: 8px;
+}
+
+.card-title {
+  font-weight: 600;
+  color: #303133;
+}
+
+.table-card {
+  border-radius: 8px;
+}
+
+.total-text {
+  float: right;
+  color: #909399;
+  font-size: 14px;
+}
+
+.compact-form {
+  margin-bottom: -18px;
+}
+
+.compact-form .el-form-item {
+  margin-bottom: 16px;
+}
+
+.form-actions {
+  margin-left: auto;
+}
+
+.operation-row {
+  margin-bottom: 16px;
+}
+
+.custom-table {
+  border-radius: 8px;
+}
+
+.custom-table ::v-deep .el-table__header th {
+  background-color: #f5f7fa;
+  color: #606266;
+  font-weight: 600;
+}
+
+.pagination-container {
+  margin-top: 20px;
+  text-align: right;
+  padding: 16px;
+  background: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.el-tag {
+  margin: 2px;
+}
+</style>
+
+<style>
+.compact-form .el-form-item__label {
+  padding-right: 8px;
+}
+</style>

+ 1611 - 0
src/views/qw/groupVoiceMsg/detailsRecord/statistics.vue

@@ -0,0 +1,1611 @@
+<template>
+  <div class="ai-statistics-container">
+
+    <!-- 统计卡片区域 -->
+    <div class="stats-dashboard">
+      <el-row :gutter="24">
+        <el-col :xs="24" :sm="12" :md="6">
+          <div class="stat-card stat-session-card">
+            <div class="card-content">
+              <div class="stat-value">{{ formatNumber(totalSession) }}</div>
+              <div class="stat-label">总会话数</div>
+            </div>
+          </div>
+        </el-col>
+        <el-col :xs="24" :sm="12" :md="6">
+          <div class="stat-card stat-token-card">
+            <div class="card-content">
+              <div class="stat-value">{{ formatNumber(totalToken) }}</div>
+              <div class="stat-label">总消耗Token</div>
+            </div>
+          </div>
+        </el-col>
+        <el-col :xs="24" :sm="12" :md="6">
+          <div class="stat-card stat-input-card">
+            <div class="card-content">
+              <div class="stat-value">{{ formatNumber(chatPromptToken) }}</div>
+              <div class="stat-label">入参消耗Token</div>
+            </div>
+          </div>
+        </el-col>
+        <el-col :xs="24" :sm="12" :md="6">
+          <div class="stat-card stat-output-card">
+            <div class="card-content">
+              <div class="stat-value">{{ formatNumber(chatCompletionToken) }}</div>
+              <div class="stat-label">出参消耗Token</div>
+            </div>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+
+    <!-- 筛选区域 -->
+    <el-card shadow="hover" class="filter-card">
+      <div class="filter-header">
+        <span class="filter-title"><i class="el-icon-search"></i> 筛选条件</span>
+        <el-button-group>
+          <el-button v-for="preset in presets" :key="preset.value" :type="activePreset === preset.value ? 'primary' : 'default'" size="small" round @click="selectPreset(preset.value)">{{ preset.label }}</el-button>
+        </el-button-group>
+      </div>
+      <el-form :inline="true" :model="queryParams" class="filter-form">
+        <el-form-item>
+          <el-date-picker v-model="queryParams.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd" size="small" style="width: 260px" @change="handleDateChange" />
+        </el-form-item>
+        <el-form-item>
+          <el-select v-model="queryParams.aiSopCallRoleId" placeholder="选择外呼角色" clearable filterable size="small" style="width: 160px" @change="handleQuery">
+            <el-option v-for="item in aiRoleOptions" :key="item.id" :label="item.callRoleName" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" size="small" icon="el-icon-search" @click="handleQuery" v-hasPermi="['qw:groupVoiceMsgUser:details:statistics']">搜索</el-button>
+          <el-button size="small" icon="el-icon-refresh" @click="resetQuery">重置</el-button>
+          <el-button type="success" size="small" icon="el-icon-download" @click="handleExport" v-hasPermi="['qw:groupVoiceMsgUser:details:exportStatistics']">导出</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <!-- 数据展示区域 -->
+    <el-card shadow="never" class="data-table-card">
+      <div slot="header" class="table-header">
+        <div class="header-left">
+          <i class="el-icon-data-analysis"></i>
+          <span class="header-title">智能数据分析</span>
+          <el-tag size="mini" type="info">共 {{ total }} 条记录</el-tag>
+        </div>
+        <div class="header-actions">
+          <el-button
+            type="text"
+            icon="el-icon-refresh"
+            :loading="loading"
+            @click="getList"
+            class="refresh-btn"
+          />
+        </div>
+      </div>
+      <el-table
+        v-loading="loading"
+        :data="detailsList"
+        stripe
+        border
+        class="modern-table"
+        :header-cell-style="headerStyle"
+        @sort-change="handleSortChange"
+      >
+        <el-table-column type="index" label="序号" width="100" align="center" />
+        <el-table-column prop="aiSopCallRoleName" label="外呼角色" width="200" sortable>
+          <template slot-scope="{row}">
+            <div class="role-cell">
+              <div class="role-avatar">
+                <i class="el-icon-service"></i>
+                <div class="avatar-glow"></div>
+              </div>
+              <span class="role-name">{{ row.aiSopCallRoleName || '-' }}</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="durationMsStr" label="总时长" width="200" align="center" sortable />
+        <el-table-column prop="totalSession" label="总会话数" align="center" sortable>
+          <template slot-scope="{row}">
+            <span class="metric-value primary">{{ formatNumber(row.totalSession) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="totalToken" label="总Token" align="center" sortable>
+          <template slot-scope="{row}">
+            <span class="metric-value success">{{ formatNumber(row.totalToken) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="chatPromptToken" label="输入Token" align="center" sortable>
+          <template slot-scope="{row}">
+            <span class="metric-value warning">{{ formatNumber(row.chatPromptToken) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="chatCompletionToken" label="输出Token" align="center" sortable>
+          <template slot-scope="{row}">
+            <span class="metric-value info">{{ formatNumber(row.chatCompletionToken) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createTime" label="创建时间" min-width="160" align="center" sortable />
+        <el-table-column label="操作" width="120" align="center" fixed="right">
+          <template slot-scope="{row}">
+            <div class="action-cell">
+              <el-tooltip content="查看详细数据" placement="top">
+                <el-button
+                  type="primary"
+                  size="mini"
+                  icon="el-icon-view"
+                  circle
+                  @click="handleView(row)"
+                  v-hasPermi="['qw:groupVoiceMsgUser:details:list']"
+                  class="view-btn"
+                />
+              </el-tooltip>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="table-footer">
+        <div class="pagination-info">
+          <span>显示第 {{ (queryParams.pageNum - 1) * queryParams.pageSize + 1 }} 到 {{ Math.min(queryParams.pageNum * queryParams.pageSize, total) }} 条,共 {{ total }} 条记录</span>
+        </div>
+        <pagination
+          v-show="total>0"
+          :total="total"
+          :page.sync="queryParams.pageNum"
+          :limit.sync="queryParams.pageSize"
+          @pagination="getList"
+          class="modern-pagination"
+        />
+      </div>
+    </el-card>
+
+
+    <!-- 数据展示页面 -->
+    <el-dialog
+      :title="`会话详情 - ${currentRow.aiSopCallRoleName || '未知客服'}`"
+      :visible.sync="detailDialogVisible"
+      width="85%"
+      top="3vh"
+      append-to-body
+      class="detail-dialog-enhanced"
+      :close-on-click-modal="false"
+      :modal-append-to-body="true"
+    >
+      <div v-loading="detailLoading" class="dialog-content-wrapper">
+        <!-- 数据表格 -->
+        <el-card shadow="never" class="detail-table-card">
+          <div slot="header" class="table-header-enhanced">
+            <span><i class="el-icon-chat-line-round"></i> 会话记录详情</span>
+            <el-tag type="info" size="medium">共 {{ detailTotal }} 条记录</el-tag>
+          </div>
+          <el-table
+            :data="detailList"
+            stripe
+            border
+            v-loading="detailLoading"
+            style="width: 100%"
+            class="clean-table"
+          >
+            <el-table-column prop="sessionId" label="会话ID" align="center" min-width="120" />
+            <el-table-column prop="externalUserName" label="联系人" align="center" min-width="100" />
+            <el-table-column prop="satisfaction" label="满意度" align="center" min-width="100">
+              <template slot-scope="{row}">
+                <el-tag
+                  :type="row.satisfaction === 3 ? 'success' : row.satisfaction === 2 ? 'warning' : 'danger'"
+                  size="small"
+                  effect="dark"
+                >
+                  <i :class="row.satisfaction === 3 ? 'el-icon-success' : row.satisfaction === 2 ? 'el-icon-warning' : 'el-icon-error'"></i>
+                  {{ formatSatisfaction(row.satisfaction) }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column prop="totalToken" label="总Token" align="center" min-width="100" />
+            <el-table-column prop="chatPromptToken" label="输入Token" align="center" min-width="100" />
+            <el-table-column prop="chatCompletionToken" label="输出Token" align="center" min-width="100" />
+            <el-table-column prop="aiChatDetails" label="内容" align="center" min-width="300">
+              <template slot-scope="{row}">
+                <div
+                  class="content-text"
+                  :title="formatChatContent(row.aiChatDetails, row.aiSopCallRoleName, row.externalUserName)"
+                >
+                  {{ truncateText(formatChatContent(row.aiChatDetails, row.aiSopCallRoleName, row.externalUserName), 3) }}
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="语音" align="center" min-width="140">
+              <template slot-scope="{row}">
+                <div v-if="isValidVoiceData(row.voiceText)" class="audio-container">
+                  <audio
+                      :src="getAudioSource(row.voiceText)"
+                      controls
+                      style="width: 120px; height: 32px"
+                      @error="handleAudioError"
+                      preload="none"
+                      :title="isAudioFailed(getAudioSource(row.voiceText)) ? '音频加载失败' : '语音播放'"
+                  ></audio>
+                  <el-tag
+                      v-if="isAudioFailed(getAudioSource(row.voiceText))"
+                      size="mini"
+                      type="danger"
+                      class="audio-error"
+                  >
+                    加载失败
+                  </el-tag>
+                  <el-tooltip
+                      v-else-if="Array.isArray(row.voiceText) && row.voiceText.length > 1"
+                      content="点击查看所有语音">
+                    <el-tag size="mini" type="info" class="audio-count">
+                      +{{ row.voiceText.length - 1 }}
+                    </el-tag>
+                  </el-tooltip>
+                </div>
+                <span v-else class="no-data">-</span>
+              </template>
+            </el-table-column>
+
+            <el-table-column prop="createTime" label="创建时间" align="center" min-width="150" />
+          </el-table>
+
+          <!-- 分页 -->
+          <pagination
+              v-show="detailTotal>0"
+              :total="detailTotal"
+              :page.sync="detailQuery.pageNum"
+              :limit.sync="detailQuery.pageSize"
+              @pagination="handleDetailPagination"
+              class="detail-pagination"
+          />
+        </el-card>
+      </div>
+
+      <div slot="footer" class="dialog-footer-enhanced">
+        <el-button @click="detailDialogVisible = false" class="close-btn">
+          <i class="el-icon-close"></i> 关闭
+        </el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listDetails,statisticsAiCallDetails,exportStatistics } from "@/api/qw/groupVoiceMsgUserDetails";
+import { listRole } from "@/api/aiSopCall/aiSopCallRole";
+import Pagination from "@/components/Pagination";
+
+export default {
+  name: "detailsStatistics",
+  components: { Pagination },
+  data() {
+    return {
+      // AI智能分析相关
+      aiAnalysisDialogVisible: false,
+      aiAnalysisLoading: false,
+
+      // 查看详情相关数据
+      detailDialogVisible: false,
+      detailLoading: false,
+      currentRow: {},
+      detailData: {},
+      detailList: [],
+      detailTotal: 0,
+      detailQuery: {
+        pageNum: 1,
+        pageSize: 10,
+        sessionId: null
+      },
+
+      // 时间快捷选项
+      presets: [
+        { value: 'today', label: '今天' },
+        { value: 'week', label: '本周' },
+        { value: 'month', label: '本月' },
+        { value: 'quarter', label: '本季度' },
+        { value: 'year', label: '本年' }
+      ],
+      activePreset: 'month',
+
+      // 统计卡片数据
+      totalSession: 0,
+      totalToken: 0,
+      chatPromptToken: 0,
+      chatCompletionToken: 0,
+      failedAudioSources: new Set(), // 跟踪加载失败的音频
+      // 外呼角色选项
+      aiRoleOptions: [],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 总条数
+      total: 0,
+      // AI外呼客服呼叫详情表格数据
+      detailsList: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        aiSopCallRoleId: null,
+        aiSopCallRoleName: null,
+        externalUserId: null,
+        externalUserName: null,
+        dateRange: [],
+        startTime: null,
+        endTime: null
+      },
+      // 查询参数
+      queryDetailsParams: {
+        pageNum: 1,
+        pageSize: 10,
+        aiSopCallRoleId: null,
+        startTime: null,
+        endTime: null
+      },
+      // 表头样式
+      headerStyle: {
+        background: '#ffffff',
+        color: '#4c5161',
+        fontWeight: '600',
+        fontSize: '14px',
+        textAlign: 'center'
+      }
+    };
+  },
+  created() {
+    // 设置默认时间范围:本月第一天到当前时间
+    this.setDefaultDateRange();
+    this.loadAiRoles();
+    this.getList();
+  },
+
+  methods: {
+
+    // 设置默认时间范围
+    setDefaultDateRange() {
+      const now = new Date();
+      const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
+
+      this.queryParams.dateRange = [
+        this.formatDate(firstDay),
+        this.formatDate(now)
+      ];
+      this.queryParams.startTime = this.formatDate(firstDay);
+      this.queryParams.endTime = this.formatDate(now);
+    },
+
+
+    // 格式化日期
+    formatDate(date) {
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    },
+
+    /** 日期变化处理 */
+    handleDateChange(val) {
+      if (val && val.length >= 2) {
+        this.queryParams.startTime = val[0];
+        this.queryParams.endTime = val[1];
+      } else {
+        this.queryParams.startTime = null;
+        this.queryParams.endTime = null;
+      }
+    },
+
+    /** 加载外呼角色列表 */
+    loadAiRoles() {
+      listRole({ pageSize: 100, pageNum: 1 }).then(response => {
+        if (response.code === 200) {
+          this.aiRoleOptions = response.rows || [];
+        }
+      }).catch(error => {
+        console.error('加载外呼角色失败:', error);
+        this.$message.error('加载外呼角色失败');
+      });
+    },
+
+    /** 查询AI外呼客服呼叫详情列表 */
+    getList() {
+      this.loading = true;
+      statisticsAiCallDetails(this.queryParams).then(response => {
+        this.detailsList = response.rows || [];
+        this.total = response.total || 0;
+        this.calculateStatistics();
+        this.loading = false;
+      });
+    },
+
+    /** 计算统计数据 */
+    calculateStatistics() {
+      const stats = { totalSession: 0, totalToken: 0, chatPromptToken: 0, chatCompletionToken: 0 };
+      this.detailsList.forEach(item => {
+        stats.totalSession += (item.totalSession || 0);
+        stats.totalToken += (item.totalToken || 0);
+        stats.chatPromptToken += (item.chatPromptToken || 0);
+        stats.chatCompletionToken += (item.chatCompletionToken || 0);
+      });
+      this.totalSession = stats.totalSession;
+      this.totalToken = stats.totalToken;
+      this.chatPromptToken = stats.chatPromptToken;
+      this.chatCompletionToken = stats.chatCompletionToken;
+    },
+
+    /** 格式化数字 */
+    formatNumber(num) {
+      if (num === null || num === undefined) return '0';
+      return num.toLocaleString();
+    },
+
+    /** 格式化聊天内容 */
+    formatChatContent(content, roleName, externalName) {
+      if (!content) return '';
+      return content
+        .replace(/\[客服\]/g, roleName || '客服')
+        .replace(/\[客户\]/g, externalName || '客户');
+    },
+
+    /** 格式化满意度 */
+    formatSatisfaction(value) {
+      if (value === 1) return '低';
+      if (value === 2) return '中';
+      if (value === 3) return '高';
+      return '-';
+    },
+
+    /** 截断文本显示 */
+    truncateText(text, maxLines = 2) {
+      if (!text) return '-';
+      const lines = text.split('\n');
+      if (lines.length <= maxLines) return text;
+      return lines.slice(0, maxLines).join('\n') + '...';
+    },
+
+    /** 格式化语音文本 */
+    formatVoiceText(voiceText) {
+      if (!voiceText) return '';
+      if (Array.isArray(voiceText)) {
+        return voiceText.join(', ');
+      }
+      if (typeof voiceText === 'string') {
+        return voiceText;
+      }
+      return String(voiceText);
+    },
+
+    /** 获取音频源 */
+    getAudioSource(voiceText) {
+      if (!this.isValidVoiceData(voiceText)) {
+        return '';
+      }
+
+      // 处理数组形式的语音
+      if (Array.isArray(voiceText)) {
+        // 找到第一个有效的音频URL
+        const validUrl = voiceText.find(url => this.isValidAudioUrl(url));
+        return validUrl || '';
+      }
+
+      // 处理字符串形式的语音
+      if (typeof voiceText === 'string') {
+        if (this.isValidAudioUrl(voiceText)) {
+          let url = voiceText.trim();
+
+          // 验证URL格式
+          try {
+            // 如果已经是完整URL,则直接返回
+            if (url.startsWith('http://') || url.startsWith('https://')) {
+              return url;
+            }
+
+            // 如果是相对路径,添加基础URL
+            if (url.startsWith('/')) {
+              return url;
+            }
+
+            // 如果是有效的URL片段,构建完整URL
+            if (url && !url.startsWith('http') && !url.startsWith('/') && !url.startsWith('blob:')) {
+              return process.env.VUE_APP_BASE_API + '/' + url;
+            }
+
+            return url;
+          } catch (e) {
+            console.error('URL格式错误:', e);
+            return '';
+          }
+        }
+      }
+
+      return '';
+    },
+
+    /**
+     * 验证语音URL格式是否有效
+     * @param {string} url - 待验证的URL
+     * @returns {boolean} - 是否为有效的语音URL格式
+     */
+    isValidAudioUrl(url) {
+      if (!url || typeof url !== 'string') {
+        return false;
+      }
+
+      // 去除首尾空白字符
+      url = url.trim();
+
+      // 检查是否为空
+      if (!url) {
+        return false;
+      }
+
+      // 定义有效的音频文件扩展名
+      const validExtensions = ['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac'];
+      const lowerCaseUrl = url.toLowerCase();
+
+      // 检查URL是否包含有效的音频扩展名
+      const hasValidExtension = validExtensions.some(ext => lowerCaseUrl.includes(ext));
+
+      // 如果包含有效扩展名,直接返回true
+      if (hasValidExtension) {
+        return true;
+      }
+
+      // 检查是否为有效的HTTP/HTTPS URL
+      try {
+        const parsedUrl = new URL(url);
+        return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:';
+      } catch (e) {
+        // 如果URL解析失败,检查是否为相对路径且包含音频相关信息
+        return /^\/.*\.(mp3|wav|ogg|m4a|aac|flac)/i.test(url) ||
+            /audio/i.test(url) ||
+            /voice/i.test(url) ||
+            /record/i.test(url);
+      }
+    },
+
+    /**
+     * 检查语音数据是否有效
+     * @param {any} voiceText - 语音数据
+     * @returns {boolean} - 语音数据是否有效
+     */
+    isValidVoiceData(voiceText) {
+      if (!voiceText) {
+        return false;
+      }
+
+      if (Array.isArray(voiceText)) {
+        // 数组情况下,检查是否有至少一个有效的音频URL
+        return voiceText.some(url => this.isValidAudioUrl(url));
+      }
+
+      // 字符串情况下,检查是否为有效的音频URL
+      return this.isValidAudioUrl(voiceText);
+    },
+
+    /** 处理音频错误 */
+    handleAudioError(event) {
+      console.error('音频播放失败:', event);
+      const audioElement = event.target;
+      const src = audioElement.getAttribute('src');
+
+      // 将失败的源添加到集合中
+      if (src) {
+        this.failedAudioSources.add(src);
+      }
+
+      // 清除当前音频源
+      audioElement.src = '';
+      audioElement.removeAttribute('src');
+
+      // 停止加载
+      audioElement.load();
+    },
+    /** 检查音频是否加载失败 */
+    isAudioFailed(src) {
+      return this.failedAudioSources.has(src);
+    },
+
+    /** 重置音频失败状态 */
+    resetAudioFailures() {
+      this.failedAudioSources.clear();
+    },
+
+    /** 截断语音文本显示 */
+    truncateVoiceText(voiceText) {
+      const formatted = this.formatVoiceText(voiceText);
+      if (!formatted) return '-';
+
+      const lines = formatted.split(',');
+      if (lines.length <= 2) return formatted;
+      return lines.slice(0, 2).join(',') + '...';
+    },
+
+    /** 选择快捷时间 */
+    selectPreset(value) {
+      this.activePreset = value;
+      const now = new Date();
+      let start = now;
+
+      switch (value) {
+        case 'today':
+          start = new Date(now.setHours(0, 0, 0, 0));
+          break;
+        case 'week':
+          start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
+          start = new Date(start.setHours(0, 0, 0, 0));
+          break;
+        case 'month':
+          start = new Date(now.getFullYear(), now.getMonth(), 1);
+          break;
+        case 'quarter':
+          start = new Date(now.getFullYear(), Math.floor(now.getMonth() / 3) * 3, 1);
+          break;
+        case 'year':
+          start = new Date(now.getFullYear(), 0, 1);
+          break;
+      }
+
+      this.queryParams.dateRange = [this.formatDate(start), this.formatDate(now)];
+      this.queryParams.startTime = this.formatDate(start);
+      this.queryParams.endTime = this.formatDate(now);
+      this.handleQuery();
+    },
+    /** 详情分页处理 */
+    handleDetailPagination() {
+      this.getDetailList();
+    },
+    /** 查看详情 */
+    handleView(row) {
+      this.currentRow = row;
+      // 重置分页参数
+      this.detailQuery.pageNum = 1;
+      this.detailQuery.pageSize = 10;
+      this.getDetailList();
+    },
+    getDetailList(){
+      this.detailDialogVisible = true;
+      this.queryDetailsParams = {
+        pageNum: this.detailQuery.pageNum,
+        pageSize: this.detailQuery.pageSize,
+        aiSopCallRoleId: this.currentRow.aiSopCallRoleId,
+        startTime: this.currentRow.startTime,
+        endTime: this.currentRow.endTime
+      };
+      listDetails(this.queryDetailsParams).then(response => {
+        this.detailList = response.rows || [];
+        this.detailTotal = response.total || 0;
+        this.detailLoading = false;
+      }).catch(() => {
+        this.detailLoading = false;
+        this.$message.error('获取详情列表失败');
+      });
+    },
+
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    // 分页大小变化
+    handleSizeChange(val) {
+      this.queryParams.pageSize = val;
+      this.getList();
+    },
+    // 当前页码变化
+    handleCurrentChange(val) {
+      this.queryParams.pageNum = val;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      // 重置时间范围为默认值(本月第一天到当前时间)
+      this.setDefaultDateRange();
+      // 重置预设时间为本月
+      this.activePreset = 'month';
+      this.handleQuery();
+    },
+
+
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有AI外呼客服统计数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportStatistics(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
+    },
+
+    // 排序处理
+    handleSortChange({ column, prop, order }) {
+      console.log('排序字段:', prop, '排序方式:', order);
+      // 可以在这里添加自定义排序逻辑
+    },
+
+    // 计算效率
+    calculateEfficiency() {
+      if (!this.currentRow.totalSession || !this.currentRow.totalToken) return '0';
+      const efficiency = (this.currentRow.totalSession / this.currentRow.totalToken * 10000).toFixed(1);
+      return efficiency;
+    },
+
+    // 预测下月数据
+    predictNextMonth() {
+      if (!this.currentRow.totalSession) return '0';
+      // 简单的线性预测
+      const predicted = Math.round(this.currentRow.totalSession * 1.15);
+      return this.formatNumber(predicted);
+    },
+
+    // 获取优化建议
+    getRecommendations() {
+      const tips = [
+        '建议优化提示词以提高响应准确性',
+        '可以考虑调整模型温度参数',
+        '增加训练数据可提升模型表现',
+        '定期更新知识库保持时效性'
+      ];
+      return tips;
+    },
+
+    // 导出AI报告
+    exportAIReport() {
+      this.$message.success('AI分析报告已导出');
+      this.aiAnalysisDialogVisible = false;
+    },
+
+    // 格式化时长
+    formatDuration(durationStr) {
+      if (!durationStr) return '00:00:00';
+      return durationStr;
+    }
+  }
+};
+</script>
+
+<style scoped>
+.ai-statistics-container {
+  padding: 20px;
+  background: #ffffff;
+  min-height: 100vh;
+}
+
+.search-card {
+  margin-bottom: 20px;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  background: #ffffff;
+}
+
+.card-title {
+  font-weight: 600;
+  color: #303133;
+}
+
+.table-card {
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  background: #ffffff;
+}
+
+.total-text {
+  float: right;
+  color: #909399;
+  font-size: 14px;
+}
+
+.compact-form {
+  margin-bottom: -18px;
+}
+
+.form-actions {
+  margin-left: auto;
+}
+
+.operation-row {
+  margin-bottom: 16px;
+}
+
+.custom-table {
+  border-radius: 8px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
+}
+
+.custom-table ::v-deep .el-table__header th {
+  background: #ffffff;
+  color: #4c5161;
+  font-weight: 600;
+  border-color: #e2e8f0;
+  text-align: center;
+}
+
+.custom-table ::v-deep .el-table__row td {
+  text-align: center;
+  vertical-align: middle;
+  border-color: #e2e8f0;
+}
+
+.pagination-container {
+  margin-top: 20px;
+  text-align: right;
+  padding: 16px;
+  background: #ffffff;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+}
+
+/* 统计仪表盘 */
+.stats-dashboard {
+  margin-bottom: 24px;
+  padding: 0;
+}
+
+.stat-card {
+  position: relative;
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 20px;
+  transition: all 0.3s ease;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  background: #ffffff;
+  border: 1px solid #e2e8f0;
+  height: 140px;
+  display: flex;
+  flex-direction: column;
+}
+
+.stat-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+}
+
+.stat-session-card {
+  border-top: 4px solid #667eea;
+}
+
+.stat-token-card {
+  border-top: 4px solid #11998e;
+}
+
+.stat-input-card {
+  border-top: 4px solid #f2994a;
+}
+
+.stat-output-card {
+  border-top: 4px solid #2193b0;
+}
+
+.card-icon-wrapper {
+  width: 40px;
+  height: 40px;
+  border-radius: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 18px;
+  margin-bottom: 12px;
+  color: white;
+}
+
+.session-icon {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+.token-icon {
+  background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
+}
+
+.input-icon {
+  background: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%);
+}
+
+.output-icon {
+  background: linear-gradient(135deg, #2193b0 0%, #6dd5ed 100%);
+}
+
+.card-content {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.stat-value {
+  font-size: 28px;
+  font-weight: 700;
+  line-height: 1.2;
+  margin-bottom: 6px;
+}
+
+.stat-label {
+  font-size: 13px;
+  opacity: 0.8;
+  margin-bottom: 0;
+  font-weight: 500;
+}
+
+
+
+
+
+/* 智能筛选卡片 */
+.smart-filter-card {
+  border-radius: 16px;
+  border: none;
+  margin-bottom: 32px;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
+  background: #ffffff;
+  overflow: hidden;
+}
+
+.smart-filter-card ::v-deep .el-card__header {
+  background: #ffffff;
+  border-bottom: 1px solid #e2e8f0;
+  padding: 20px 24px;
+}
+
+.filter-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 16px;
+}
+
+.filter-title-section {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  font-size: 18px;
+  font-weight: 600;
+  color: #2d3748;
+}
+
+.filter-title-section i {
+  color: #667eea;
+  font-size: 20px;
+}
+
+.preset-buttons {
+  display: flex;
+  gap: 8px;
+}
+
+.preset-btn {
+  transition: all 0.3s ease;
+  border: 1px solid #e2e8f0;
+}
+
+.preset-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.filter-form {
+  padding: 24px;
+}
+
+.filter-row {
+  display: flex;
+  align-items: end;
+  gap: 20px;
+  flex-wrap: wrap;
+}
+
+.filter-row ::v-deep .el-form-item {
+  margin-bottom: 0;
+}
+
+.filter-row ::v-deep .el-form-item__label {
+  font-weight: 500;
+  color: #4a5568;
+}
+
+.date-picker {
+  width: 280px;
+}
+
+.role-select {
+  width: 200px;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 12px;
+  margin-left: auto;
+}
+
+.search-btn {
+  background: #667eea;
+  border: none;
+  transition: all 0.3s ease;
+}
+
+.search-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
+}
+
+.reset-btn {
+  border: 1px solid #cbd5e0;
+  color: #4a5568;
+  transition: all 0.3s ease;
+}
+
+.reset-btn:hover {
+  background: #f7fafc;
+  transform: translateY(-1px);
+}
+
+.export-btn {
+  background: #11998e;
+  border: none;
+  transition: all 0.3s ease;
+}
+
+.export-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 6px 20px rgba(17, 153, 142, 0.4);
+}
+
+/* 数据表格卡片 */
+.data-table-card {
+  border-radius: 16px;
+  border: none;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
+  background: #ffffff;
+  overflow: hidden;
+}
+
+.data-table-card ::v-deep .el-card__header {
+  background: #ffffff;
+  padding: 20px 24px;
+  border-bottom: 1px solid #e2e8f0;
+}
+
+.table-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  color: #2d3748;
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+.header-title {
+  font-size: 18px;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.header-title i {
+  font-size: 20px;
+}
+
+
+
+.header-actions {
+  display: flex;
+  gap: 12px;
+}
+
+.refresh-btn {
+  color: #667eea;
+  font-size: 18px;
+  transition: all 0.3s ease;
+}
+
+.refresh-btn:hover {
+  transform: rotate(180deg);
+}
+
+.modern-table {
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid #e2e8f0;
+}
+
+.modern-table ::v-deep .el-table__header th {
+  background: #ffffff;
+  color: #4c5161;
+  font-weight: 600;
+  text-align: center;
+  height: 56px;
+  padding: 8px 4px;
+  border: 1px solid #e2e8f0;
+}
+
+.modern-table ::v-deep .el-table__body td {
+  padding: 16px 4px;
+  text-align: center;
+  vertical-align: middle;
+  border-color: #edf2f7;
+  transition: background-color 0.2s ease;
+  color: #000000;
+}
+
+.modern-table ::v-deep .el-table__row:hover td {
+  background-color: #f8f9fa !important;
+}
+
+.modern-table ::v-deep .el-table__row:nth-child(even) td {
+  background-color: #fdfdfd;
+}
+
+.role-cell {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  justify-content: center;
+}
+
+.role-avatar {
+  position: relative;
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+  background: #667eea;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 16px;
+  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+}
+
+
+
+.role-name {
+  font-weight: 500;
+  color: #2d3748;
+}
+
+.action-cell {
+  display: flex;
+  justify-content: center;
+  gap: 8px;
+}
+
+.view-btn {
+  background: #ffffff;
+  border: 1px solid #000000;
+  transition: all 0.3s ease;
+  color: #000000;
+}
+
+.view-btn:hover {
+  transform: translateY(-2px) scale(1.1);
+  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
+  background: #f0f0f0;
+}
+
+.ai-btn {
+  background: #ffffff;
+  border: 1px solid #000000;
+  transition: all 0.3s ease;
+  color: #000000;
+}
+
+.ai-btn:hover {
+  transform: translateY(-2px) scale(1.1);
+  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
+  background: #f0f0f0;
+}
+
+.table-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 24px;
+  background: #ffffff;
+  border-top: 1px solid #e2e8f0;
+}
+
+.pagination-info {
+  color: #718096;
+  font-size: 14px;
+}
+
+.modern-pagination ::v-deep .el-pagination {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.modern-pagination ::v-deep .el-pager li {
+  margin: 0 4px;
+  border-radius: 8px;
+  transition: all 0.3s ease;
+}
+
+.modern-pagination ::v-deep .el-pager li:hover {
+  color: #667eea;
+  transform: translateY(-2px);
+}
+
+/* AI分析对话框 */
+.ai-analysis-dialog ::v-deep .el-dialog {
+  border-radius: 16px;
+  overflow: hidden;
+  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
+}
+
+.ai-analysis-dialog ::v-deep .el-dialog__header {
+  background: #ffffff;
+  padding: 24px;
+  color: #2d3748;
+  border-bottom: 1px solid #e2e8f0;
+}
+
+.ai-analysis-dialog ::v-deep .el-dialog__title {
+  color: #2d3748;
+  font-weight: 600;
+  font-size: 20px;
+}
+
+.ai-analysis-content {
+  padding: 24px;
+}
+
+.analysis-header {
+  display: flex;
+  align-items: center;
+  gap: 20px;
+  margin-bottom: 32px;
+  padding: 20px;
+  background: #ffffff;
+  border-radius: 12px;
+  border: 1px solid #f0f0f0;
+}
+
+.ai-avatar {
+  position: relative;
+  width: 60px;
+  height: 60px;
+  border-radius: 50%;
+  background: #667eea;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 24px;
+}
+
+.ai-pulse {
+  position: absolute;
+  top: -4px;
+  left: -4px;
+  right: -4px;
+  bottom: -4px;
+  border-radius: 50%;
+  border: 2px solid rgba(102, 126, 234, 0.4);
+  animation: aiPulse 2s infinite;
+}
+
+@keyframes aiPulse {
+  0% { transform: scale(0.8); opacity: 1; }
+  100% { transform: scale(1.5); opacity: 0; }
+}
+
+.analysis-info h3 {
+  margin: 0 0 8px 0;
+  color: #2d3748;
+  font-size: 20px;
+}
+
+.analysis-info p {
+  margin: 0;
+  color: #718096;
+  font-size: 14px;
+}
+
+.analysis-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 24px;
+  margin-bottom: 32px;
+}
+
+.analysis-card {
+  background: #ffffff;
+  border-radius: 12px;
+  padding: 20px;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+  border: 1px solid #e2e8f0;
+  transition: all 0.3s ease;
+}
+
+.analysis-card:hover {
+  transform: translateY(-4px);
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+}
+
+.card-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 16px;
+  color: #2d3748;
+  font-weight: 600;
+  font-size: 16px;
+}
+
+.card-header i {
+  color: #667eea;
+  font-size: 18px;
+}
+
+.metric, .prediction {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12px 0;
+  border-bottom: 1px solid #edf2f7;
+}
+
+.metric:last-child, .prediction:last-child {
+  border-bottom: none;
+}
+
+.metric-label, .prediction-label {
+  color: #718096;
+  font-size: 14px;
+}
+
+.metric-value, .prediction-value {
+  font-weight: 700;
+  font-size: 16px;
+  /* 移除了固定颜色设置,使用默认文本颜色 */
+}
+
+/* 已移除 .metric-value.success 的颜色样式 */
+
+.prediction-trend {
+  padding: 4px 12px;
+  border-radius: 16px;
+  font-size: 12px;
+  font-weight: 600;
+}
+
+.prediction-trend.up {
+  background: rgba(56, 239, 125, 0.2);
+  color: #11998e;
+}
+
+.recommendations {
+  background: #ffffff;
+  border-radius: 12px;
+  padding: 24px;
+  border: 1px solid #f0f0f0;
+}
+
+.section-title {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 20px;
+  color: #2d3748;
+  font-weight: 600;
+  font-size: 18px;
+}
+
+.section-title i {
+  color: #f6e05e;
+  font-size: 20px;
+}
+
+.recommendation-list {
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+
+.recommendation-item {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 12px 0;
+  border-bottom: 1px solid #edf2f7;
+  color: #4a5568;
+}
+
+.recommendation-item:last-child {
+  border-bottom: none;
+}
+
+.recommendation-item i {
+  color: #38ef7d;
+  font-size: 16px;
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 16px;
+  padding: 20px 24px;
+  background: #ffffff;
+  border-top: 1px solid #e2e8f0;
+}
+
+.close-btn {
+  background: #e2e8f0;
+  border: 1px solid #cbd5e0;
+  color: #4a5568;
+  padding: 10px 24px;
+  transition: all 0.3s ease;
+}
+
+.close-btn:hover {
+  background: #cbd5e0;
+  transform: translateY(-2px);
+}
+
+.export-btn {
+  background: #667eea;
+  border: none;
+  color: white;
+  padding: 10px 24px;
+  transition: all 0.3s ease;
+}
+
+.export-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
+}
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .page-header {
+    flex-direction: column;
+    text-align: center;
+    gap: 20px;
+  }
+
+  .analysis-grid {
+    grid-template-columns: 1fr;
+  }
+
+  .filter-row {
+    flex-direction: column;
+    align-items: stretch;
+  }
+
+  .action-buttons {
+    margin-left: 0;
+    justify-content: center;
+  }
+
+  .ai-statistics-container {
+    background: #ffffff;
+  }
+}
+
+@media (max-width: 768px) {
+  .ai-statistics-container {
+    padding: 16px;
+    background: #ffffff;
+  }
+
+  .page-header {
+    padding: 20px;
+  }
+
+  .page-title {
+    font-size: 24px;
+  }
+
+  .stat-card {
+    padding: 20px;
+    height: 140px;
+  }
+
+  .stat-value {
+    font-size: 28px;
+  }
+
+  .filter-form {
+    padding: 20px;
+  }
+
+  .date-picker, .role-select {
+    width: 100%;
+  }
+
+  .table-footer {
+    flex-direction: column;
+    gap: 16px;
+    align-items: stretch;
+  }
+
+  .pagination-info {
+    text-align: center;
+  }
+
+  .ai-powered-badge {
+    position: static;
+    margin-bottom: 16px;
+    justify-content: center;
+  }
+}
+
+@media (max-width: 480px) {
+  .stat-card {
+    padding: 16px;
+    height: 120px;
+  }
+
+  .stat-value {
+    font-size: 24px;
+  }
+
+  .stat-label {
+    font-size: 13px;
+  }
+
+  .card-icon-wrapper {
+    width: 48px;
+    height: 48px;
+    font-size: 20px;
+  }
+
+  .page-title {
+    font-size: 20px;
+  }
+
+  .analysis-header {
+    flex-direction: column;
+    text-align: center;
+    gap: 16px;
+  }
+
+  .dialog-footer {
+    flex-direction: column;
+  }
+
+  .export-btn, .close-btn {
+    width: 100%;
+  }
+
+  .ai-statistics-container {
+    background: #ffffff;
+  }
+}
+
+/* 性能优化 */
+.stat-card, .analysis-card, .smart-filter-card, .data-table-card {
+  will-change: transform, box-shadow;
+  backface-visibility: hidden;
+  transform: translateZ(0);
+}
+
+/* 滚动条美化 */
+::-webkit-scrollbar {
+  width: 6px;
+  height: 6px;
+}
+
+::-webkit-scrollbar-track {
+  background: #f8f9fa;
+  border-radius: 3px;
+}
+
+::-webkit-scrollbar-thumb {
+  background: #667eea;
+  border-radius: 3px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+  background: #5a6fd8;
+}
+</style>

+ 1151 - 0
src/views/qw/groupVoiceMsg/index.vue

@@ -0,0 +1,1151 @@
+<template>
+  <div>
+    <div class="app-container">
+    </div>
+    <div class="app-container">
+      <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+        <el-form-item label="企微公司" prop="corpId">
+                        <el-select v-model="queryParams.corpId" placeholder="请选择企微公司"  size="small" @change="updateCorpId()">
+                          <el-option
+                            v-for="dict in myQwCompanyList"
+                            :key="dict.dictValue"
+                            :label="dict.dictLabel"
+                            :value="dict.dictValue"
+                          />
+                        </el-select>
+        </el-form-item>
+        <el-form-item label="AI客服" prop="aiCustomerRoleId">
+          <el-select
+            v-model="queryParams.aiCustomerRoleId"
+            placeholder="请选择AI客服角色"
+            clearable
+            size="small"
+            @keyup.enter.native="handleQuery">
+            <el-option
+              v-for="item in aiCustomerRoleOptions"
+              :key="item.id"
+              :label="item.callRoleName"
+              :value="item.id">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置/刷新页面</el-button>
+          <el-button type="primary" size="mini" @click="handleAdd()" v-hasPermi="['qw:groupVoiceMsg:add']">新建群呼</el-button>
+        </el-form-item>
+      </el-form>
+      <el-row :gutter="10" class="mb8">
+        <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table v-loading="loading" :data="groupMsgList" @selection-change="handleSelectionChange" border>
+        <el-table-column label="AI外呼客服" align="left" prop="aiCallRoleName"/>
+        <el-table-column label="任务类型" align="center" prop="chatType">
+          <template slot-scope="scope">
+            <span v-if="scope.row.chatType === 1">AI外呼群发</span>
+            <span v-else-if="scope.row.chatType === 2">手动</span>
+            <span v-else-if="scope.row.chatType === 3">sop模板提醒</span>
+            <span v-else-if="scope.row.chatType === 4">新客对话</span>
+            <span v-else-if="scope.row.chatType === 5">sop任务</span>
+            <span v-else>{{scope.row.chatType}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="呼叫时效" align="center" prop="isTimerSend">
+          <template slot-scope="scope">
+            <span v-if="scope.row.isTimerSend === 1">定时呼叫</span>
+            <span v-else-if="scope.row.isTimerSend === 2">立即呼叫</span>
+            <span v-else>{{scope.row.isTimerSend}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="呼叫时间" align="center">
+          <template slot-scope="scope">
+            <span v-if="scope.row.isTimerSend===2">{{scope.row.createTime}}</span>
+            <span v-else>{{ scope.row.timerSend }}</span>
+          </template>
+        </el-table-column>
+
+
+        <el-table-column label="呼叫状态" align="center"  width="110px">
+          <template slot-scope="scope">
+            <el-tag
+              v-if="scope.row.isSend===1"
+              type="success"
+              size="mini"
+              plain>
+              呼叫成功
+            </el-tag >
+            <el-tag
+              v-else-if="scope.row.isSend===0"
+              type="danger"
+              size="mini"
+              plain>
+              呼叫失败
+            </el-tag >
+            <el-tag
+              v-else-if="scope.row.isSend===3"
+              type="warning"
+              size="mini"
+              plain>
+              待呼叫
+            </el-tag >
+          </template>
+        </el-table-column>
+        <el-table-column label="已呼数" align="center" prop="calledCallUserSum" />
+        <el-table-column label="待呼数" align="center" prop="pendingCalledCallUserSum" />
+        <el-table-column label="失败数" align="center" prop="failCallUserSum" />
+        <el-table-column label="总呼数" align="center" prop="callUserSum" />
+        <el-table-column label="创建人" align="center" prop="createName" width="120px" />
+        <el-table-column label="创建时间" align="center" prop="createTime" width="160px"/>
+        <el-table-column label="操作" align="center" class-name="small-padding" width="180px" fixed="right">
+          <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-notebook-2"
+              @click="hangdleDitels(scope.row)"
+              v-hasPermi="['qw:groupVoiceMsg:query']"
+            >详情</el-button>
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-delete"
+              @click="handleDelete(scope.row)"
+              v-hasPermi="['qw:groupVoiceMsg:remove']"
+            >删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total>0"
+        :total="total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getList"
+      />
+
+      <!-- 添加客户群发记录主对话框 -->
+      <el-dialog v-loading="loading" :title="title" :visible.sync="open" width="1500px" @close="closeData" style="font-weight: bold;font-size: 20px" append-to-body>
+        <div style="display: flex;flex-wrap: wrap">
+          <div style="width: 1000px;">
+            <el-form ref="form" :model="form" :rules="rules" label-width="160px" size="medium" style="font-size: 20px" >
+              <div>
+                <span style="font-size: 20px">基础信息</span>
+                <el-divider></el-divider>
+              </div>
+
+              <el-form-item label="选择外呼成员账号:" prop="userIdsSelectList" style="font-size: 30px">
+                <div>
+                  <div style="display: flex; align-items: center; margin-bottom: 10px;">
+                    <el-button
+                      size="medium"
+                      icon="el-icon-circle-plus-outline"
+                      plain
+                      @click="handlelistUser">请选择外呼账号</el-button>
+                    <div style="margin-left: 15px; color: #909399; font-size: 14px; display: flex; align-items: center;">
+                      <i class="el-icon-info" style="margin-right: 5px;"></i>
+                      <span>外呼账号需先在【系统管理】-【员工管理】-【操作】-【绑定外呼角色】后才能选择外呼成员</span>
+                    </div>
+                  </div>
+                  <div>
+                    <el-tag
+                      style="margin-left: 5px; margin-top: 5px;"
+                      size="medium"
+                      :key="list.id"
+                      v-for="list in userSelectList"
+                      closable
+                      :disable-transitions="false"
+                      @close="handleClosegroupUser(list)">
+                      {{list.qwUserName}}({{list.nickName}})
+                    </el-tag>
+                  </div>
+                </div>
+              </el-form-item>
+
+
+              <el-form-item label="选择客户:">
+                <el-radio-group v-model="form.selectType" @input="clearExpectCountCustomer" size="medium ">
+                  <el-radio :label="item.dictValue" v-for="item in groupMsgTypeOptions"  >{{item.dictLabel}}</el-radio>
+                </el-radio-group>
+                <div style="background-color:#ecf8fe;width: 100%;margin-top: 1%" v-if="form.selectType==1">
+                  <span style="padding: 3%;font-size: 15px" >查看该消息预计呼叫人数:</span>
+                  <el-link style="color: #1aa4ff;" v-if="this.expectCountCustomer==null" @click="viewReceiveNum">点我查看
+                  </el-link>
+                  <span style="color: #d440ec;margin-left: 2%" v-if="this.expectCountCustomer!=null">{{this.expectCountCustomer}} 人</span>
+                </div>
+                <div v-if="form.selectType==2" style="padding-top: 1%;background-color: #fbfbfb">
+                  <el-form-item label="按性别筛选客户:" class="elFormItemClass">
+                    <el-radio-group v-model="form.gender"  @input="clearExpectCountCustomer">
+                      <el-radio v-for="item in sysUserSex" :label="item.dictValue">{{item.dictLabel}}</el-radio>
+                    </el-radio-group>
+                  </el-form-item>
+                  <el-form-item label="按所在群聊筛选:" class="elFormItemClass">
+                    <div>
+                      <div>
+                        <el-button
+                          icon="el-icon-s-comment"
+                          plain
+                          @click="handleGroupChatList">请选择群聊</el-button>
+                      </div>
+                      <div>
+                        <div>
+                          <el-tag
+                            style="margin-left: 5px"
+                            size="medium"
+                            :key="list.chatId"
+                            v-for="list in groupChatList"
+                            closable
+                            :disable-transitions="false"
+                            @close="handleClosegroupChat(list)">
+                            {{list.name}}
+                          </el-tag>
+
+                        </div>
+                      </div>
+                    </div>
+
+                  </el-form-item>
+                  <el-form-item label="按添加时间筛选" class="elFormItemClass">
+                    <div style="width: 100%;display: flex">
+                      <el-date-picker
+                        v-model="form.timeScreenCharge"
+                        @change="clearExpectCountCustomer"
+                        type="daterange"
+                        align="right"
+                        unlink-panels
+                        range-separator="至"
+                        start-placeholder="开始日期"
+                        end-placeholder="结束日期"
+                        :picker-options="pickerOptions"
+                        :default-time="['00:00:00', '23:59:59']">
+                      </el-date-picker>
+                    </div>
+
+                  </el-form-item>
+                  <el-form-item label="按客户标签筛选:" class="elFormItemClass">
+                    <div>
+                      <div>
+                        <el-button icon="el-icon-s-check" plain @click="hangleChangeTags">请选择标签</el-button>
+                      </div>
+                      <el-tag type="success"
+                              closable
+                              :disable-transitions="false"
+                              v-for="list in tagsIdsChangeSelectList"
+                              :key="list.tagId"
+                              @close="handleCloseTags(list)"
+                              style="margin: 3px;"
+                      >{{list.name}}
+                      </el-tag>
+                    </div>
+                  </el-form-item>
+
+                  <el-form-item label="排除标签筛选" class="elFormItemClass">
+                    <div>
+                      <div>
+                        <el-button icon="el-icon-s-check" plain @click="hangleChangeOutTags">请选择排除的标签</el-button>
+                      </div>
+
+                      <div>
+                        <el-tag type="success"
+                                closable
+                                :disable-transitions="false"
+                                v-for="list in outTagsIdsChangeSelectList"
+                                :key="list.tagId"
+                                @close="handleCloseOutTags(list)"
+                                style="margin: 3px;"
+                        >{{list.name}}
+                        </el-tag>
+                      </div>
+
+                    </div>
+                  </el-form-item>
+
+                  <div style="background-color:#ecf8fe;width: 100%;height: 10%;margin-top: 3%">
+                    <span style="margin-left: 2%;font-size: 15px" v-if="this.expectCountGroupChat==null">查看该消息预计呼叫人数:</span>
+                    <el-link style="color: #1aa4ff;margin-left: 3%" v-if="this.expectCountGroupChat==null" @click="viewReceiveGroupChatNum">点我查看
+                    </el-link>
+                    <span style="color: #d440ec;margin-left: 2%" v-if="this.expectCountGroupChat!=null">{{this.expectCountGroupChat}} 人</span>
+                  </div>
+                </div>
+
+
+              </el-form-item>
+              <div>
+                <span>AI外呼客服选择</span>
+                <el-divider></el-divider>
+              </div>
+            </el-form>
+          </div>
+        </div>
+
+        <WelComeAddAIVoice ref="WelComeAddAIVoice"></WelComeAddAIVoice>
+
+        <div slot="footer" class="dialog-footer" style="display: flex;justify-content: center">
+          <el-button type="primary" @click="submitForm">确定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </el-dialog>
+
+      <!-- 选择群发账号弹窗   -->
+      <el-dialog :title="listUser.title" :visible.sync="listUser.open" width="1600px"  append-to-body>
+        <qwUserList ref="qwUserList" @selectUserList="selectUserList"></qwUserList>
+      </el-dialog>
+
+      <!-- 选择群聊    -->
+      <el-dialog :title="groupChart.title" :visible.sync="groupChart.open" width="1300px"  append-to-body>
+        <group-chat ref="GroupChat" @selectGroupChatList="selectGroupChatList"></group-chat>
+      </el-dialog>
+
+      <!--  选择的标签   -->
+      <el-dialog :title="changeTagDialog.title" :visible.sync="changeTagDialog.open" width="1000px"  append-to-body>
+
+        <div>搜索标签:
+          <el-input v-model="tagChange.tagName" placeholder="请输入标签名称" clearable size="small" style="width: 200px;margin-right: 10px" />
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleSearchTags(tagChange.tagName)">搜索</el-button>
+          <el-button type="primary" icon="el-icon-plus" size="mini" @click="cancelSearchTags">重置</el-button>
+        </div>
+        <div v-for="item in tagGroupList" :key="item.id"  >
+          <div style="font-size: 20px;margin-top: 20px;margin-bottom: 20px;">
+            <span class="name-background">{{ item.name }}</span>
+          </div>
+          <div class="tag-container">
+            <a
+              v-for="tagItem in item.tag"
+              class="tag-box"
+              @click="tagSelection(tagItem)"
+              :class="{ 'tag-selected': tagItem.isSelected }"
+            >
+              {{ tagItem.name }}
+            </a>
+          </div>
+        </div>
+        <div slot="footer" class="dialog-footer">
+          <el-button type="primary" @click="tagSubmitForm(changeTagDialog.type)">确 定</el-button>
+          <el-button @click="tagCancel(changeTagDialog.type)">取消</el-button>
+        </div>
+      </el-dialog>
+
+      <!--  详情  -->
+      <el-drawer :title="detailsDialog.title" :visible.sync="detailsDialog.open" size="80%" style="font-weight: bolder">
+        <div style="background-color:  rgb(240 242 245)">
+          <div style="width: 100%">
+            <customer-group-details ref="customerGroupDetails" :rowDetailFrom="formDetails"></customer-group-details>
+          </div>
+        </div>
+      </el-drawer>
+    </div>
+
+  </div>
+</template>
+
+<script>
+import {
+  myList,
+  getGroupVoiceMsg,
+  delGroupVoiceMsg,
+  addGroupVoiceMsg, remindGroupVoiceMsg
+} from '@/api/qw/groupVoiceMsg'
+import GroupMsgUser from '@/views/qw/groupMsgUser/groupMsg'
+import ImageUpload from "@/views/qw/material/ImageUpload";
+import qwUserList from "@/views/qw/user/qwUserList"
+import { selectCountCustomer } from '@/api/qw/externalContact'
+import CustomerGroupDetails from '@/views/qw/groupVoiceMsg/customerGroupDetails'
+import { getMyQwCompanyList } from "@/api/qw/user";
+import {allListTagGroup} from "@/api/qw/tagGroup";
+import {listTag, searchTags} from "@/api/qw/tag";
+import  MaterialQw  from "@/views/qw/materialQw/index.vue";
+import WelComeAddAIVoice from "../welcome/welComeAddAIVoice.vue";
+import {listRole} from "../../../api/aiSopCall/aiSopCallRole";
+export default {
+  name: "GroupVoiceMsg",
+  components: { CustomerGroupDetails, GroupMsgUser,ImageUpload,qwUserList,WelComeAddAIVoice,MaterialQw},
+  data() {
+    return {
+      imageArr:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      myQwCompanyList:[],
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      dialogImageUrl:null,
+      dialogVisible:false,
+
+      // 客户群发记录主表格数据
+      groupMsgList: [],
+
+      //发送类型
+      groupMsgTimerType:[],
+      //呼叫ai客服角色
+      aiCustomerRoleOptions: [],
+
+      //选择客户类型
+      groupMsgTypeOptions:[],
+      //是否允许成员在待发送列表中再次选择
+      allowSelectOptions:[],
+
+      //消息类型
+      msgTypeOptions:[],
+      //客户性别
+      sysUserSex:[],
+      genderSex:null,
+      //预期发送客户人数
+      expectCountCustomer:null,
+
+      //部分客户预期发送客户人数
+      expectCountGroupChat:null,
+
+      //客户群选择列表
+      groupChatList:[],
+
+      //成员选择列表
+      userSelectList:[],
+
+      //查询预计通知人数
+      countList:{
+        //根据成员查
+        userIds:{},
+        //根据筛选客户条件查
+        //1.群聊
+        chartIds:{},
+
+      },
+
+      //选择的标签
+      tagsIdsChangeSelectList:null,
+      //排除的标签
+      outTagsIdsChangeSelectList:null,
+
+      //选择群发账号列表
+      listUser:{
+        title:"",
+        open:false
+      },
+      //选择所在群聊里的人
+      groupChart:{
+        title:"",
+        open:false,
+      },
+
+      //标签弹窗选择
+      tagChange:{
+        open:false,
+        index:null,
+      },
+      //选择标签
+      changeTagDialog:{
+        title:"",
+        open:false,
+        type:null,
+      },
+      detailsDialog:{
+        title:"",
+        open:false,
+        type:null,
+      },
+      //标签组
+      tagGroupList:[],
+      //所有标签
+      tagList:[],
+
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        chatType: 1,
+        sender: null,
+        allowSelect: null,
+        textContent: null,
+        msgid: null,
+        qwMsgId:null,
+        corpId: null,
+        companyId: null,
+        tagNames: null,
+        groupChatNames: null,
+        fromUserCount: null,
+        toUserCount: null,
+        fromUserNum: null,
+        toUserNum: null,
+        aiCustomerRoleId: null,
+      },
+      // 表单参数
+      form: {},
+      //详情
+      formDetails:{},
+      // 表单校验
+      rules: {
+
+        chatType: [
+          { required: true, message: "群发任务类型 好几种暂定 1AI外呼群发的  2手动的 3 sop模板提醒的", trigger: "change" }
+        ],
+
+        userIdsSelectList: [
+          { required: true, message: "发送企业群发消息的成员账号不能为空", trigger: "blur" }
+        ],
+        allowSelect: [
+          { required: true, message: "是否允许成员在待发送客户列表中重新进行选择,默认为true", trigger: "blur" }
+        ],
+        textContent:[
+          { required: true, message: "消息文本不能为空噢", trigger: "blur" }
+        ],
+        createName:[
+          { required: true, message: "创建人不能为空", trigger: "blur" }
+        ]
+
+      },
+      //日期快捷选择
+      pickerOptions: {
+        shortcuts: [
+          {
+            text: '最近三天',
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 3);
+              picker.$emit('pick', [start, end]);
+            }
+          },{
+            text: '最近一周',
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+              picker.$emit('pick', [start, end]);
+            }
+          }, {
+            text: '最近一个月',
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+              picker.$emit('pick', [start, end]);
+            }
+          }, {
+            text: '最近三个月',
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+              picker.$emit('pick', [start, end]);
+            }
+          }]
+      },
+
+    };
+  },
+  created() {
+    getMyQwCompanyList().then(response => {
+            this.myQwCompanyList = response.data;
+            if(this.myQwCompanyList!=null){
+              this.queryParams.corpId=this.myQwCompanyList[0].dictValue;
+              this.getList();
+            }
+    });
+
+    listRole().then(response => {
+      this.aiCustomerRoleOptions = response.rows;
+    });
+
+    //全部客户还是部分客户
+    this.getDicts("sys_qw_groupMsg_type").then(response => {
+      this.groupMsgTypeOptions = response.data;
+    });
+
+    //文本类型字典(复用)
+    this.getDicts("sys_qw_welcome_type").then(response => {
+      this.msgTypeOptions = response.data;
+    });
+
+
+    //发送类型
+    this.getDicts("sys_qw_group_msg_timer_type").then(response => {
+      this.groupMsgTimerType = response.data;
+    });
+
+    //客户性别
+    this.getDicts("sys_qw_external_contact_gender").then(response => {
+      this.sysUserSex = response.data;
+
+      // 要添加的新对象数据
+      let newUserData = {
+        dictValue: "99",
+        dictLabel: '全部',
+      };
+      // 添加对象到数组
+      this.sysUserSex.push(newUserData);
+
+    });
+
+    //是否允许成员在待发送列表中再次选择
+    this.getDicts("sys_qw_allow_select").then(response => {
+      this.allowSelectOptions = response.data;
+    });
+  },
+
+  watch:{
+    // 监听 userSelectList 的变化,并同步更新 form.userIdsSelectList
+    userSelectList(newList) {
+      this.form.userIdsSelectList = newList.map(item => item.qwUserId);
+    },
+    groupChatList(newList){
+      this.form.groupChatSelectList=newList.map(item=>item.chatId)
+    },
+  },
+  methods: {
+    updateCorpId(){
+           this.getList();
+     },
+
+    //关闭弹窗-清空页面
+    closeData(){
+      this.reset();
+    },
+    /** 查询客户群发记录主列表 */
+    getList() {
+      this.loading = true;
+      myList(this.queryParams).then(response => {
+        this.groupMsgList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+
+    //标签的选择
+    tagSelection(row){
+      row.isSelected= !row.isSelected;
+      this.$forceUpdate();
+    },
+    /** 提醒成员群发 */
+    triggerremindGroupVoiceMsg(row){
+      this.queryParams.qwMsgId=row.msgId
+      remindGroupVoiceMsg(this.queryParams).then(res=>{
+
+        if (res.code==200){
+
+          if (res.data.errcode==0){
+            return this.msgSuccess("提醒成员群发成功")
+          }else if (res.data.errcode==41094){
+            return this.msgWarning("此条记录的每日提醒次数超过限制了噢")
+          }else {
+            return this.msgError(res.data.errmsg)
+          }
+
+        }else {
+          return this.msgError(res.msg)
+        }
+      })
+    },
+    handleSearchTags(name){
+
+      searchTags({name:name,corpId:this.queryParams.corpId}).then(response => {
+        this.tagGroupList = response.rows;
+      });
+
+    },
+    cancelSearchTags(){
+      this.afreshData()
+    },
+    //清除点我查看
+    clearExpectCountCustomer(){
+
+      //选择全部客户的统计
+      this.expectCountCustomer=null;
+      //选择部分客户的统计
+      this.expectCountGroupChat=null;
+    },
+
+    //查看预计接收人数
+    viewReceiveNum(){
+      if (this.userSelectList.length===0){
+        return this.msgError("请选择群发成员账号");
+      }else {
+        this.form.corpId=this.queryParams.corpId;
+        console.log("this.queryParams.corpId="+ this.queryParams.corpId);
+        selectCountCustomer(this.form).then(res=>{
+          this.expectCountCustomer=res.data;
+        })
+      }
+    },
+
+    //查看部分客户预计接收人数
+    viewReceiveGroupChatNum(){
+
+      if (this.userSelectList.length===0){
+        return  this.msgError("请选择群发成员账号");
+      }else {
+
+        // 使用 map 方法和解构赋值提取每个子数组的第一个元素
+        // 选择的标签
+        if (this.tagsIdsChangeSelectList!=null){
+          this.form.tagsIdsSelectList=this.tagsIdsChangeSelectList.map(item => item.tagId);
+        }
+
+        //排除的标签
+        if (this.outTagsIdsChangeSelectList!=null){
+          this.form.outTagsIdsSelectList = this.outTagsIdsChangeSelectList.map(item => item.tagId);
+        }
+        this.form.corpId=this.queryParams.corpId;
+        selectCountCustomer(this.form).then(res=>{
+          this.expectCountGroupChat=res.data;
+        })
+
+      }
+    },
+
+    //选择的成员账号列表
+    selectUserList(list){
+      this.listUser.open=false;
+      //用于显示
+      this.userSelectList=list;
+
+    },
+
+    //选择的部分客户时,选择的群聊里的
+    selectGroupChatList(list){
+
+      this.groupChart.open=false;
+      //用于显示
+      this.groupChatList=list;
+
+    },
+    //确定选择标签
+    tagSubmitForm(type){
+        if (type===1){
+          for (let i = 0; i < this.tagGroupList.length; i++) {
+            for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+              if (this.tagGroupList[i].tag[x].isSelected === true) {
+
+                if (!this.tagsIdsChangeSelectList) {
+                  this.tagsIdsChangeSelectList = [];
+                }
+
+                // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+                let tagExists = this.tagsIdsChangeSelectList.some(
+                  tag => tag.id === this.tagGroupList[i].tag[x].id
+                );
+
+                // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+                if (!tagExists) {
+                  this.tagsIdsChangeSelectList.push(this.tagGroupList[i].tag[x]);
+                }
+              }
+            }
+          }
+          if (!this.tagsIdsChangeSelectList || this.tagsIdsChangeSelectList.length === 0) {
+            return this.$message('请选择标签');
+          }
+
+        }else if (type===2){
+
+          for (let i = 0; i < this.tagGroupList.length; i++) {
+            for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+              if (this.tagGroupList[i].tag[x].isSelected === true) {
+
+                if (!this.outTagsIdsChangeSelectList) {
+                  this.outTagsIdsChangeSelectList = [];
+                }
+
+                // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+                let tagExists = this.outTagsIdsChangeSelectList.some(
+                  tag => tag.id === this.tagGroupList[i].tag[x].id
+                );
+
+                // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+                if (!tagExists) {
+                  this.outTagsIdsChangeSelectList.push(this.tagGroupList[i].tag[x]);
+                }
+              }
+            }
+          }
+          if (!this.outTagsIdsChangeSelectList || this.outTagsIdsChangeSelectList.length === 0) {
+            return this.$message('请选择标签');
+          }
+
+        }
+
+
+
+      this.changeTagDialog.open = false;
+    },
+
+    //取消选择标签
+    tagCancel(){
+      this.changeTagDialog.open = false;
+    },
+
+    //删除一些选择了的账号
+    handleClosegroupUser(list){
+
+      // 假设 list 对象具有一个 id 属性
+      const index = this.userSelectList.findIndex(t => t.id === list.id);
+      if (index !== -1) {
+        this.userSelectList.splice(index, 1);
+      }
+      //表单同时清除
+      // this.form.userIdsSelectList= this.userSelectList.map(item => item.id);
+    },
+
+    //删除一些选择了的群聊
+    handleClosegroupChat(list){
+
+      // 假设 list 对象具有一个 id 属性
+      const index = this.groupChatList.findIndex(t => t.chatId === list.chatId);
+      if (index !== -1) {
+        this.groupChatList.splice(index, 1);
+      }
+      // //表单同时清除
+      // this.form.groupChatSelectList=this.groupChatList.map(item=>item.chatId);
+    },
+
+    //删除一些选择的标签
+    handleCloseTags(list){
+      // 假设 list 对象具有一个 id 属性
+      const ls = this.tagsIdsChangeSelectList.findIndex(t => t.tagId === list.tagId);
+      if (ls !== -1) {
+        this.tagsIdsChangeSelectList.splice(ls, 1);
+        this.tagsIdsChangeSelectList = [...this.tagsIdsChangeSelectList];
+      }
+    },
+
+    //删除一些排除的标签
+    handleCloseOutTags(list){
+      // 假设 list 对象具有一个 id 属性
+      const ls = this.outTagsIdsChangeSelectList.findIndex(t => t.tagId === list.tagId);
+      if (ls !== -1) {
+        this.outTagsIdsChangeSelectList.splice(ls, 1);
+        this.outTagsIdsChangeSelectList = [...this.outTagsIdsChangeSelectList];
+      }
+    },
+
+    //选择群发的企业成员账号
+    handlelistUser(){
+      this.listUser.title="选择企业成员"
+      this.listUser.open=true;
+      setTimeout(() => {
+        this.$refs.qwUserList.getBindAiUser(this.queryParams.corpId,1)
+      }, 200);
+
+
+      //预计人数制空
+      this.expectCountCustomer=null;
+    },
+
+    //选择群聊
+    handleGroupChatList(){
+
+      this.groupChart.title="选择所在群聊里的客户"
+      this.groupChart.open=true;
+
+      setTimeout(() => {
+        this.$refs.GroupChat.getDetailsList(this.queryParams.corpId)
+      }, 200);
+
+      //预计人数制空
+      this.expectCountGroupChat=null;
+
+    },
+
+    //选择标签
+    hangleChangeTags(){
+
+      this.changeTagDialog.title="选择标签"
+      this.changeTagDialog.open=true;
+      this.changeTagDialog.type=1;
+
+      // 获取 tagListFormIndex 中的所有 tagId,用于快速查找
+      const selectedTagIds = new Set(this.tagsIdsChangeSelectList.map(tagItem => tagItem.tagId));
+      for (let i = 0; i < this.tagGroupList.length; i++) {
+        for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+          this.tagGroupList[i].tag[x].isSelected = selectedTagIds.has(this.tagGroupList[i].tag[x].tagId);
+        }
+      }
+
+      // setTimeout(() => {
+      //    this.$refs.TagGroupList.getTagGroupList(this.queryParams.corpId)
+      // }, 200);
+
+
+      //预计人数制空
+      this.expectCountGroupChat=null;
+    },
+
+    //选择排除标签
+    hangleChangeOutTags(){
+      this.changeTagDialog.title="选择排除的标签"
+      this.changeTagDialog.open=true;
+      this.changeTagDialog.type=2;
+
+      // 获取 tagListFormIndex 中的所有 tagId,用于快速查找
+      const selectedTagIds = new Set(this.outTagsIdsChangeSelectList.map(tagItem => tagItem.tagId));
+      for (let i = 0; i < this.tagGroupList.length; i++) {
+        for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+          this.tagGroupList[i].tag[x].isSelected = selectedTagIds.has(this.tagGroupList[i].tag[x].tagId);
+        }
+      }
+
+      // setTimeout(() => {
+      //   this.$refs.TagGroupList.getTagGroupList(this.queryParams.corpId)
+      // }, 200);
+      //
+
+      //预计人数制空
+      this.expectCountGroupChat=null;
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        chatType: 1,
+        sender: null,
+        gender:"99",
+        allowSelect: "Y",
+        textContent: null,
+        msgid: null,
+        corpId: null,
+        companyId: null,
+        tagNames: null,
+        groupChatNamesList: [],
+        fromUserCount: null,
+        toUserCount: null,
+        fromUserNum: null,
+        toUserNum: null,
+        createTime: null,
+        updateTime: null,
+        msgType:null,
+        mediaId: null,
+        mediaPicUrl: null,
+        title: null,
+        picurl: null,
+        description: null,
+        url: null,
+        miniprogramTitle: null,
+        miniprogramPicMediaId: null,
+        miniprogramPicUrl: null,
+        miniprogramAppid: null,
+        miniprogramPage: null,
+        fileMediaId: null,
+        fileUrl: null,
+        videoMediaId: null,
+        videoUrl: null,
+        //选择的成员账号
+        userIdsSelectList:[],
+        //选择的群聊
+        groupChatSelectList:[],
+        //选择的时间
+        timeScreenCharge:[],
+
+        //全部客户还是部分客户
+        selectType:'1',
+
+        //选择的标签列表
+        tagsIdsSelectList:[],
+
+        //排除的标签
+        outTagsIdsSelectList:[],
+      };
+      this.userSelectList=[];
+      this.expectCountCustomer=null;
+      this.groupChatList=[];
+      this.tagsIdsChangeSelectList=[];
+      this.outTagsIdsChangeSelectList=[];
+      this.expectCountGroupChat=[];
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+
+    hangdleDitels(row){
+      this.reset();
+      const id = row.id || this.ids
+      getGroupVoiceMsg(id).then(response => {
+        this.formDetails = response.data;
+        this.detailsDialog.open = true;
+        this.detailsDialog.title = "客户外呼记录";
+      });
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      // 检查是否选择了企微公司
+      if (!this.queryParams.corpId) {
+        this.msgError("请先选择要操作的企微公司");
+        return;
+      }
+      this.reset();
+      this.open = true;
+      this.form.msgType="1"
+      this.title = "新建外呼";
+      this.afreshData();
+    },
+    /**
+     * 重新获取 部分数据
+     */
+    afreshData(){
+      //所有的标签组
+      allListTagGroup({corpId:this.queryParams.corpId}).then(response => {
+        this.tagGroupList = response.rows;
+      });
+
+      //所有的标签
+      listTag({corpId:this.queryParams.corpId}).then(response => {
+        this.tagList = response.rows;
+      });
+
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getGroupVoiceMsg(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改客户群发记录主";
+      });
+    },
+
+    openImageViewer(url) {
+      // 打开大图预览对话框
+      this.dialogImageUrl=url
+      this.dialogVisible = true;
+    },
+
+    /** 提交按钮 */
+    async  submitForm() {
+      // 选择的标签
+      if (this.tagsIdsChangeSelectList!=null){
+        this.form.tagsIdsSelectList=this.tagsIdsChangeSelectList.map(item => item.tagId);
+      }
+
+      //排除的标签
+      if (this.outTagsIdsChangeSelectList!=null){
+        this.form.outTagsIdsSelectList = this.outTagsIdsChangeSelectList.map(item => item.tagId);
+      }
+      this.form.corpId=this.queryParams.corpId;
+      try {
+        // 验证子组件表单
+        await this.$refs.WelComeAddAIVoice.validateChildForm();
+        // 父组件表单验证逻辑
+        await this.$refs["form"].validate(valid => {
+          if (valid) {
+            if (this.form.id != null) {
+              return this.msgError("没有修改选项");
+            } else {
+              this.loading=true;
+              Object.assign(this.form, this.$refs.WelComeAddAIVoice.form);
+
+              this.form.chatType= 1 ;
+
+              addGroupVoiceMsg(this.form).then(response => {
+                  this.msgSuccess("创建成功");
+                  this.open = false;
+                  this.loading = false;
+                  this.getList();
+              }).catch(error => {
+                this.open=true;
+                this.loading = false;
+                this.msgError("创建失败:"+ error);
+              }).finally(
+                  () => {
+                    this.$refs["form"].resetFields();
+                    this.$refs.WelComeAddAIVoice.reset();
+                    this.userSelectList=[];
+                    this.reset();
+                  }
+              );
+            }
+          }
+        });
+
+      } catch (error) {
+        console.error('表单编辑群发信息验证不用过:', error.message);
+      }
+
+
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除客户群发记录主编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delGroupVoiceMsg(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+  }
+};
+</script>
+<style scoped>
+.elFormItemClass{
+  margin: 20px 0px
+
+}
+
+.message-stayle span {
+  word-break: break-all;
+}
+
+
+/* CSS 样式 */
+.tag-container {
+  display: flex;
+  flex-wrap: wrap; /* 超出宽度时自动换行 */
+  gap: 8px; /* 设置标签之间的间距 */
+}
+.name-background {
+  display: inline-block;
+  background-color: #abece6; /* 背景颜色 */
+  padding: 4px 8px; /* 调整内边距,让背景包裹文字 */
+  border-radius: 4px; /* 可选:设置圆角 */
+}
+.tag-box {
+  padding: 8px 12px;
+  border: 1px solid #989797;
+  border-radius: 4px;
+  cursor: pointer;
+  display: inline-block;
+}
+
+.tag-selected {
+  background-color: #00bc98;
+  color: #fff;
+  border-color: #00bc98;
+}
+
+</style>

+ 306 - 0
src/views/qw/qwSopTempVoice/index.vue

@@ -0,0 +1,306 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="120px">
+<!--      <el-form-item label="销售用户ID" prop="companyUserId">-->
+<!--        <el-input-->
+<!--          v-model="queryParams.companyUserId"-->
+<!--          placeholder="请输入销售用户ID"-->
+<!--          clearable-->
+<!--          size="small"-->
+<!--          @keyup.enter.native="handleQuery"-->
+<!--        />-->
+<!--      </el-form-item>-->
+
+      <el-form-item label="语音文本" prop="voiceTxt">
+        <el-input
+          v-model="queryParams.voiceTxt"
+          placeholder="请输入语音文本"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+<!--      <el-form-item label="是否录制完成" prop="recordType">-->
+<!--        <el-select v-model="queryParams.recordType" placeholder="请选择是否录制完成" clearable size="small">-->
+<!--          <el-option-->
+<!--            v-for="dict in recordTypeOptions"-->
+<!--            :key="dict.dictValue"-->
+<!--            :label="dict.dictLabel"-->
+<!--            :value="parseInt(dict.dictValue)"-->
+<!--          />-->
+<!--        </el-select>-->
+<!--      </el-form-item>-->
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['qw:QwSopTempVoice:add']"
+        >新增</el-button>
+      </el-col>
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="success"-->
+<!--          plain-->
+<!--          icon="el-icon-edit"-->
+<!--          size="mini"-->
+<!--          :disabled="single"-->
+<!--          @click="handleUpdate"-->
+<!--          v-hasPermi="['qw:QwSopTempVoice:edit']"-->
+<!--        >修改</el-button>-->
+<!--      </el-col>-->
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="danger"-->
+<!--          plain-->
+<!--          icon="el-icon-delete"-->
+<!--          size="mini"-->
+<!--          :disabled="multiple"-->
+<!--          @click="handleDelete"-->
+<!--          v-hasPermi="['qw:QwSopTempVoice:remove']"-->
+<!--        >删除</el-button>-->
+<!--      </el-col>-->
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="QwSopTempVoiceList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="语音文本" align="center" prop="voiceTxt" />
+      <el-table-column label="秒" align="center" prop="duration" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+<!--          <el-button-->
+<!--            size="mini"-->
+<!--            type="text"-->
+<!--            icon="el-icon-edit"-->
+<!--            @click="handleUpdate(scope.row)"-->
+<!--            v-hasPermi="['qw:QwSopTempVoice:edit']"-->
+<!--          >修改</el-button>-->
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['qw:QwSopTempVoice:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改模板对应的销售语音文件对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="语音文本" prop="voiceTxt">
+          <el-input type="textarea" :rows="6" v-model="form.voiceTxt" placeholder="请输入语音文本"  />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listQwSopTempVoice, getQwSopTempVoice, delQwSopTempVoice, addQwSopTempVoice, updateQwSopTempVoice, exportQwSopTempVoice } from "@/api/qw/QwSopTempVoice";
+import {removeSidebar} from "../../../api/qw/QwSopTempVoice";
+
+export default {
+  name: "QwSopTempVoice",
+  data() {
+    return {
+
+      // 是否录制完成字典
+      recordTypeOptions: [
+        { dictLabel: '是', dictValue: '1' },
+        { dictLabel: '否', dictValue: '2' }
+      ],
+
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 模板对应的销售语音文件表格数据
+      QwSopTempVoiceList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        companyUserId: null,
+        companyId: null,
+        qwUserId: null,
+        tempId: null,
+        ruleId: null,
+        dayId: null,
+        contentId: null,
+        voiceTxt: null,
+        voiceUrl: null,
+        userVoiceUrl: null,
+        recordType: null,
+        duration: null,
+        status: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询模板对应的销售语音文件列表 */
+    getList() {
+      this.loading = true;
+      listQwSopTempVoice(this.queryParams).then(response => {
+        this.QwSopTempVoiceList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        companyUserId: null,
+        companyId: null,
+        qwUserId: null,
+        tempId: null,
+        ruleId: null,
+        dayId: null,
+        contentId: null,
+        voiceTxt: null,
+        voiceUrl: null,
+        userVoiceUrl: null,
+        recordType: null,
+        duration: null,
+        createTime: null,
+        updateTime: null,
+        status: 0
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加模板对应的销售语音文本";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getQwSopTempVoice(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改模板对应的销售语音文本";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateQwSopTempVoice(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addQwSopTempVoice(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+
+      this.$confirm('是否确认删除模板对应的销售语音文本---"' + row.voiceTxt + '"---的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return removeSidebar(row);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有模板对应的销售语音文件数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportQwSopTempVoice(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 1 - 1
src/views/qw/sopTemp/updateSopTempSXJZ.vue

@@ -1890,7 +1890,7 @@ export default {
         loading: false,
         total: 0,
         pageNum: 1,
-        pageSize: 10,
+        pageSize: 5,
         currentSetList: null,
       },
       //1 修改 2 复制 3 查看

+ 1 - 1
src/views/qw/sopUserLogsInfo/sendMsgOpenTool.vue

@@ -514,7 +514,7 @@ export default {
         loading: false,
         total: 0,
         pageNum: 1,
-        pageSize: 10,
+        pageSize: 5,
         currentItem: null,
       },
       //上传语音的遮罩层

+ 1 - 1
src/views/qw/sopUserLogsInfo/sendMsgSopOpenTool.vue

@@ -388,7 +388,7 @@ export default {
         loading: false,
         total: 0,
         pageNum: 1,
-        pageSize: 10,
+        pageSize: 5,
         currentItem: null,
       },
       //上传语音的遮罩层

+ 1 - 1
src/views/qw/sopUserLogsInfo/sopUserLogsInfoDetails.vue

@@ -758,7 +758,7 @@ export default {
         loading: false,
         total: 0,
         pageNum: 1,
-        pageSize: 10,
+        pageSize: 5,
         currentItem: null,
       },
       // sopUserLogsInfo表格数据

+ 203 - 203
src/views/qw/user/qwUserList.vue

@@ -1,61 +1,61 @@
 <template>
-  <div class="app-container">
-    <el-alert v-if="type==2 && (sendType==2||sendType==4||sendType==11)"
-      title="注意事项"
-      type="warning"
-      description="只显示含有【允许使用插件】的成员"
-      :closable="false"
-      center
-      show-icon>
-    </el-alert>
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px" @submit.prevent="handleQuery">
-
-      <el-form-item label="后台员工昵称" prop="nickName">
-        <el-input
-          v-model="queryParams.nickName"
-          placeholder="请输入后台员工昵称"
-          clearable
-          size="small"
-          @input="handleQuery"
+    <div class="app-container">
+        <el-alert v-if="type==2 && (sendType==2||sendType==4||sendType==11)"
+                  title="注意事项"
+                  type="warning"
+                  description="只显示含有【允许使用插件】的成员"
+                  :closable="false"
+                  center
+                  show-icon>
+        </el-alert>
+        <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px" @submit.prevent="handleQuery">
+
+            <el-form-item label="后台员工昵称" prop="nickName">
+                <el-input
+                    v-model="queryParams.nickName"
+                    placeholder="请输入后台员工昵称"
+                    clearable
+                    size="small"
+                    @keydown.enter.native="handleQueryEnter"
+                />
+            </el-form-item>
+            <el-form-item label="企微员工部门" prop="deptName">
+                <el-input
+                    v-model="queryParams.deptName"
+                    placeholder="请输入企微员工部门"
+                    clearable
+                    size="small"
+                    @keydown.enter.native="handleQueryEnter"
+                />
+            </el-form-item>
+
+            <el-form-item>
+                <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+                <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">刷新/重置</el-button>
+            </el-form-item>
+        </el-form>
+
+        <el-table v-loading="loading" :data="userList" ref="userList"  @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column label="企微员工账号" align="center" prop="qwUserId" />
+            <el-table-column label="企微员工昵称" align="center" prop="qwUserName"/>
+            <el-table-column label="企微员工部门" align="center" prop="departmentName"/>
+            <el-table-column label="后台员工昵称" align="center" prop="nickName"/>
+            <el-table-column label="后台员工用户名" align="center" prop="userName" />
+
+        </el-table>
+        <div style="margin-top: 30px;display: flex;justify-content: center">
+            <el-button type="warning" icon="el-icon-search" @click="confirmSelect">确定选择</el-button>
+        </div>
+
+        <pagination
+            v-show="total>0"
+            :total="total"
+            :page.sync="queryParams.pageNum"
+            :limit.sync="queryParams.pageSize"
+            @pagination="handlePaginationChange"
         />
-      </el-form-item>
-      <el-form-item label="企微员工部门" prop="deptName">
-        <el-input
-          v-model="queryParams.deptName"
-          placeholder="请输入企微员工部门"
-          clearable
-          size="small"
-          @keydown.enter.native="handleQueryEnter"
-        />
-      </el-form-item>
-
-      <el-form-item>
-        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
-        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">刷新/重置</el-button>
-      </el-form-item>
-    </el-form>
-
-    <el-table v-loading="loading" :data="userList" ref="userList"  @selection-change="handleSelectionChange">
-        <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="企微员工账号" align="center" prop="qwUserId" />
-        <el-table-column label="企微员工昵称" align="center" prop="qwUserName"/>
-        <el-table-column label="企微员工部门" align="center" prop="departmentName"/>
-        <el-table-column label="后台员工昵称" align="center" prop="nickName"/>
-        <el-table-column label="后台员工用户名" align="center" prop="userName" />
-
-    </el-table>
-    <div style="margin-top: 30px;display: flex;justify-content: center">
-      <el-button type="warning" icon="el-icon-search" @click="confirmSelect">确定选择</el-button>
     </div>
-
-    <pagination
-      v-show="total>0"
-      :total="total"
-      :page.sync="queryParams.pageNum"
-      :limit.sync="queryParams.pageSize"
-      @pagination="handlePaginationChange"
-    />
-  </div>
 </template>
 
 <script>
@@ -63,156 +63,156 @@ import { listUser, getUser, delUser, addUser, updateUser, exportUser } from "@/a
 
 
 export default {
-  name: "qwUserList",
-  data() {
-    return {
-      type:null,
-      sendType:null,
-      // 遮罩层
-      loading: true,
-      // 导出遮罩层
-      exportLoading: false,
-      // 选中数组
-      selectUsers: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
-      // 显示搜索条件
-      showSearch: true,
-      // 总条数
-      total: 0,
-      // 企微用户表格数据
-      userList: [],
-      // 弹出层标题
-      title: "",
-      // 是否显示弹出层
-      open: false,
-      // 查询参数
-      queryParams: {
-        pageNum: 1,
-        pageSize: 10,
-        qwUserId: null,
-        companyId: null,
-        companyUserId: null,
-        deptName: null,
-        corpId: null,
-        nickName: null
-      },
-      // 表单参数
-      form: {},
-
-      // 表单校验
-      rules: {
-      }
-    };
-  },
-  created() {
-
-  },
-  methods: {
-
-    getDetails(corpId,type,sendType,isRemark){
-      this.type=type;
-      this.sendType=sendType;
-      if (type!=null&&sendType!=null){
-        this.queryParams.type=type;
-        this.queryParams.sendType=sendType;
-      }
-      this.queryParams.corpId=corpId;
-      this.queryParams.isRemark=isRemark;
-      this.getList();
-    },
-    /** 查询企微用户列表 */
-    getList() {
-      this.loading = true;
-      listUser(this.queryParams).then(response => {
-        // 如果 companyUserId 为 null,移除列
-
-        this.userList = response.rows;
-        this.total =   response.total;
-        this.loading = false;
-
-      });
-    },
-    handlePaginationChange(row) {
-      this.queryParams.pageNum = row.page;
-      this.queryParams.pageSize = row.limit;
-      this.getList();
-    },
-    // 取消按钮
-    cancel() {
-      this.open = false;
-      this.reset();
-    },
-    // 表单重置
-    reset() {
-      this.form = {
-        id: null,
-        qwUserId: null,
-        companyId: null,
-        companyUserId: null,
-        corpId: null
-      };
-      this.resetForm("form");
-    },
-    //确定选择
-    confirmSelect(){
-      this.$emit("selectUserList",this.selectUsers);
-      this.resetSelect();
+    name: "qwUserList",
+    data() {
+        return {
+            type:null,
+            sendType:null,
+            // 遮罩层
+            loading: true,
+            // 导出遮罩层
+            exportLoading: false,
+            // 选中数组
+            selectUsers: [],
+            // 非单个禁用
+            single: true,
+            // 非多个禁用
+            multiple: true,
+            // 显示搜索条件
+            showSearch: true,
+            // 总条数
+            total: 0,
+            // 企微用户表格数据
+            userList: [],
+            // 弹出层标题
+            title: "",
+            // 是否显示弹出层
+            open: false,
+            // 查询参数
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+                qwUserId: null,
+                companyId: null,
+                companyUserId: null,
+                deptName: null,
+                corpId: null,
+                nickName: null
+            },
+            // 表单参数
+            form: {},
+
+            // 表单校验
+            rules: {
+            }
+        };
     },
-    //重置选择
-    resetSelect(){
-      this.$refs.userList.clearSelection();
-
-      this.selectUsers=[];
-      //重置
-      this.queryParams={
-        pageNum: 1,
-        pageSize: 5,
-        qwUserId: null,
-        companyId: null,
-        companyUserId: null,
-        corpId: null,
-        nickName: null
-      },
-        this.getList();
-    },
-    /** 搜索按钮操作 */
-    handleQuery() {
-      this.queryParams.pageNum = 1;
-      this.getList();
-    },
-    handleQueryEnter(event){
-      // 确保事件对象存在
-      if (event && event.preventDefault) {
-        event.preventDefault(); // 阻止默认提交行为
-      }
-      this.handleQuery();
-    },
-    /** 重置按钮操作 */
-    resetQuery() {
-      this.resetForm("queryForm");
-      this.handleQuery();
-    },
-
-    // 多选框选中数据
-    handleSelectionChange(selection) {
-
-      // 保存当前页的选中项
-      const currentPageSelections = selection.map(item => item.id);
-
-      // 合并选中项
-      this.selectUsers = this.selectUsers.filter(item =>
-        this.userList.some(tag => tag.id === item.id) ? currentPageSelections.includes(item.id) : true
-      ).concat(selection.filter(item => !this.selectUsers.some(selected => selected.id === item.id)));
-
-      // 更新 single 和 multiple
-      this.single = this.selectUsers.length !== 1;
-      this.multiple = !this.selectUsers.length;
+    created() {
 
     },
-
-  }
+    methods: {
+
+        getDetails(corpId,type,sendType,isRemark){
+            this.type=type;
+            this.sendType=sendType;
+            if (type!=null&&sendType!=null){
+                this.queryParams.type=type;
+                this.queryParams.sendType=sendType;
+            }
+            this.queryParams.corpId=corpId;
+            this.queryParams.isRemark=isRemark;
+            this.getList();
+        },
+        /** 查询企微用户列表 */
+        getList() {
+            this.loading = true;
+            listUser(this.queryParams).then(response => {
+                // 如果 companyUserId 为 null,移除列
+
+                this.userList = response.rows;
+                this.total =   response.total;
+                this.loading = false;
+
+            });
+        },
+        handlePaginationChange(row) {
+            this.queryParams.pageNum = row.page;
+            this.queryParams.pageSize = row.limit;
+            this.getList();
+        },
+        // 取消按钮
+        cancel() {
+            this.open = false;
+            this.reset();
+        },
+        // 表单重置
+        reset() {
+            this.form = {
+                id: null,
+                qwUserId: null,
+                companyId: null,
+                companyUserId: null,
+                corpId: null
+            };
+            this.resetForm("form");
+        },
+        //确定选择
+        confirmSelect(){
+            this.$emit("selectUserList",this.selectUsers);
+            this.resetSelect();
+        },
+        //重置选择
+        resetSelect(){
+            this.$refs.userList.clearSelection();
+
+            this.selectUsers=[];
+            //重置
+            this.queryParams={
+                pageNum: 1,
+                pageSize: 5,
+                qwUserId: null,
+                companyId: null,
+                companyUserId: null,
+                corpId: null,
+                nickName: null
+            },
+                this.getList();
+        },
+        /** 搜索按钮操作 */
+        handleQuery() {
+            this.queryParams.pageNum = 1;
+            this.getList();
+        },
+        handleQueryEnter(event){
+            // 确保事件对象存在
+            if (event && event.preventDefault) {
+                event.preventDefault(); // 阻止默认提交行为
+            }
+            this.handleQuery();
+        },
+        /** 重置按钮操作 */
+        resetQuery() {
+            this.resetForm("queryForm");
+            this.handleQuery();
+        },
+
+        // 多选框选中数据
+        handleSelectionChange(selection) {
+
+            // 保存当前页的选中项
+            const currentPageSelections = selection.map(item => item.id);
+
+            // 合并选中项
+            this.selectUsers = this.selectUsers.filter(item =>
+                this.userList.some(tag => tag.id === item.id) ? currentPageSelections.includes(item.id) : true
+            ).concat(selection.filter(item => !this.selectUsers.some(selected => selected.id === item.id)));
+
+            // 更新 single 和 multiple
+            this.single = this.selectUsers.length !== 1;
+            this.multiple = !this.selectUsers.length;
+
+        },
+
+    }
 };
 </script>

+ 248 - 0
src/views/qw/user/qwUserListWay.vue

@@ -0,0 +1,248 @@
+<template>
+  <div class="app-container">
+    <el-alert v-if="type==2 && (sendType==2||sendType==4||sendType==11)"
+      title="注意事项"
+      type="warning"
+      description="只显示含有【允许使用插件】的成员"
+      :closable="false"
+      center
+      show-icon>
+    </el-alert>
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px" @submit.prevent="handleQuery">
+
+      <el-form-item label="后台员工昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入后台员工昵称"
+          clearable
+          size="small"
+          @input="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="企微员工部门" prop="deptName">
+        <el-input
+          v-model="queryParams.deptName"
+          placeholder="请输入企微员工部门"
+          clearable
+          size="small"
+          @keydown.enter.native="handleQueryEnter"
+        />
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">刷新/重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-table v-loading="loading" :data="userList" ref="userList"  @selection-change="handleSelectionChange" :row-class-name="tableRowClassName">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="企微员工账号" align="center" prop="qwUserId" />
+        <el-table-column label="企微员工昵称" align="center" prop="qwUserName"/>
+        <el-table-column label="企微员工部门" align="center" prop="departmentName"/>
+        <el-table-column label="后台员工昵称" align="center" prop="nickName"/>
+        <el-table-column label="后台员工用户名" align="center" prop="userName" />
+        <el-table-column label="操作" align="center" width="80">
+          <template slot-scope="scope">
+            <el-button v-if="isSelectedUser(scope.row.qwUserId)" type="text" size="mini" @click="handleRemoveUser(scope.row)" style="color: #f56c6c;">移除</el-button>
+          </template>
+        </el-table-column>
+    </el-table>
+    <div style="margin-top: 30px;display: flex;justify-content: center">
+      <el-button type="warning" icon="el-icon-search" @click="confirmSelect">确定选择</el-button>
+    </div>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="handlePaginationChange"
+    />
+  </div>
+</template>
+
+<script>
+import { listUser, getUser, delUser, addUser, updateUser, exportUser } from "@/api/qw/user";
+
+
+export default {
+  name: "qwUserList",
+  data() {
+    return {
+      type:null,
+      sendType:null,
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      selectUsers: [],
+      // 已选中的员工列表(用于标记)
+      selectedUserIds: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 企微用户表格数据
+      userList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        qwUserId: null,
+        companyId: null,
+        companyUserId: null,
+        deptName: null,
+        corpId: null,
+        nickName: null
+      },
+      // 表单参数
+      form: {},
+
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+
+  },
+  methods: {
+
+    getDetails(corpId,type,sendType,isRemark, selectedUserIds){
+      this.type=type;
+      this.sendType=sendType;
+      this.selectedUserIds = selectedUserIds || [];
+      if (type!=null&&sendType!=null){
+        this.queryParams.type=type;
+        this.queryParams.sendType=sendType;
+      }
+      this.queryParams.corpId=corpId;
+      this.queryParams.isRemark=isRemark;
+      this.getList();
+    },
+    /** 查询企微用户列表 */
+    getList() {
+      this.loading = true;
+      listUser(this.queryParams).then(response => {
+        // 如果 companyUserId 为 null,移除列
+
+        this.userList = response.rows;
+        this.total =   response.total;
+        this.loading = false;
+
+      });
+    },
+    handlePaginationChange(row) {
+      this.queryParams.pageNum = row.page;
+      this.queryParams.pageSize = row.limit;
+      this.getList();
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        qwUserId: null,
+        companyId: null,
+        companyUserId: null,
+        corpId: null
+      };
+      this.resetForm("form");
+    },
+    //确定选择
+    confirmSelect(){
+      this.$emit("selectUserList",this.selectUsers);
+      this.resetSelect();
+    },
+    //重置选择
+    resetSelect(){
+      this.$refs.userList.clearSelection();
+
+      this.selectUsers=[];
+      //重置
+      this.queryParams={
+        pageNum: 1,
+        pageSize: 5,
+        qwUserId: null,
+        companyId: null,
+        companyUserId: null,
+        corpId: null,
+        nickName: null
+      },
+        this.getList();
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    handleQueryEnter(event){
+      // 确保事件对象存在
+      if (event && event.preventDefault) {
+        event.preventDefault(); // 阻止默认提交行为
+      }
+      this.handleQuery();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+
+    // 判断员工是否已选中
+    isSelectedUser(qwUserId) {
+      return this.selectedUserIds.includes(qwUserId);
+    },
+    // 表格行样式
+    tableRowClassName({row}) {
+      return this.isSelectedUser(row.qwUserId) ? 'user-selected-row' : '';
+    },
+    // 移除已选中员工
+    handleRemoveUser(user) {
+      const index = this.selectedUserIds.indexOf(user.qwUserId);
+      if (index !== -1) {
+        this.selectedUserIds.splice(index, 1);
+        this.$emit("removeUser", user);
+      }
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+
+      // 保存当前页的选中项
+      const currentPageSelections = selection.map(item => item.id);
+
+      // 合并选中项
+      this.selectUsers = this.selectUsers.filter(item =>
+        this.userList.some(tag => tag.id === item.id) ? currentPageSelections.includes(item.id) : true
+      ).concat(selection.filter(item => !this.selectUsers.some(selected => selected.id === item.id)));
+
+      // 更新 single 和 multiple
+      this.single = this.selectUsers.length !== 1;
+      this.multiple = !this.selectUsers.length;
+
+    },
+
+  }
+};
+</script>
+
+<style scoped>
+::v-deep .user-selected-row {
+  color: #67c23a !important;
+  font-weight: bold !important;
+}
+</style>

+ 211 - 0
src/views/qw/welcome/welComeAddAIVoice.vue

@@ -0,0 +1,211 @@
+<template>
+  <div style="display: flex;justify-content: normal;width: 100%">
+    <div style="width: 1000px">
+      <!-- 添加或修改入群欢迎语管理对话框 -->
+        <el-form ref="form" :model="form" :rules="rules" label-width="160px" size="medium">
+          <el-form-item label="AI外呼客服" prop="aiSopCallRoleId">
+            <el-select
+              v-model="form.aiSopCallRoleId"
+              placeholder="请选择AI外呼客服"
+              clearable
+              size="small"
+              @change="handleWorkflowIdOptionsChange">
+              <el-option
+                v-for="item in aiSopCallRoleOptions"
+                :key="item.id"
+                :label="item.callRoleName"
+                :value="item.id">
+              </el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="是否定时呼叫" prop="isTimerSend">
+            <el-radio-group v-model="form.isTimerSend" size="medium">
+              <el-radio label="1">定时呼叫</el-radio>
+              <el-radio label="2">立即呼叫</el-radio>
+            </el-radio-group>
+          </el-form-item>
+
+          <el-form-item v-if="form.isTimerSend === '1'" prop="timerSend">
+            <el-date-picker
+              v-model="form.timerSend"
+              type="datetime"
+              placeholder="选择定时呼叫时间"
+              value-format="yyyy-MM-dd HH:mm:ss"
+              default-time="09:00:00">
+            </el-date-picker>
+          </el-form-item>
+
+        </el-form>
+
+    </div>
+
+  </div>
+</template>
+
+<script>
+import ImageUpload from "@/views/qw/material/ImageUpload";
+import {listRole} from "../../../api/aiSopCall/aiSopCallRole";
+
+export default {
+  name: "Welcome",
+  components: {ImageUpload},
+
+  data() {
+    return {
+      //上传地址
+      uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS",
+      //放大图片
+      dialogImageUrl:null,
+      dialogVisible:false,
+      // 遮罩层
+      loading: true,
+      //群发文本类型(复用)
+      groupMsgTextTypeOptions:[],
+      //是否允许成员在待发送列表中再次选择
+      allowSelectOptions:[],
+      formData:null,
+
+      //呼叫ai客服角色
+      aiSopCallRoleOptions: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+
+      details:{
+        title:"",
+        open:false,
+      },
+
+      // 表单参数
+      form: {
+        aiSopCallRoleId:null,
+        aiSopCallRoleName:null,
+        allowSelect: '1',
+        msgType:'1',
+        fileUrl:null,
+        videoUrl: null,
+        isTimerSend:'2'
+      },
+      // 表单校验
+      rules: {
+        aiSopCallRoleId:[
+          {required:true,message:"请选择AI外呼客服",trigger:"blur"}
+        ],
+      }
+    };
+  },
+  created() {
+
+    //文本类型字典
+    this.getDicts("sys_qw_welcome_type").then(response => {
+      this.groupMsgTextTypeOptions = response.data;
+    });
+
+    // 修改调用方式,添加错误处理
+    listRole().then(response => {
+      this.aiSopCallRoleOptions = response.rows;
+    });
+
+    //是否允许成员在待发送列表中再次选择
+    this.getDicts("sys_qw_allow_select").then(response => {
+      this.allowSelectOptions = response.data;
+    });
+
+  },
+  methods: {
+    // 监听 AI 客服角色选择变化
+    handleWorkflowIdOptionsChange(value) {
+      if (value) {
+        const selectedOption = this.aiSopCallRoleOptions.find(item => item.id === value);
+        if (selectedOption) {
+          this.form.aiSopCallRoleName = selectedOption.callRoleName;
+        }
+      } else {
+        this.form.aiSopCallRoleName = null;
+      }
+    },
+    /** 表单验证 */
+    async validateChildForm() {
+      return new Promise((resolve, reject) => {
+        this.$refs.form.validate(async valid => {
+          if (valid) {
+            resolve(true);
+          } else {
+            console.error("表单验证失败");
+            reject(false);
+          }
+        });
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        // sender: null,
+        // gender:99,
+        allowSelect: '1',
+        textContent: null,
+        msgid: null,
+        // corpId: null,
+        // companyId: null,
+        // tagNames: null,
+        // groupChatNamesList: [],
+        // fromUserCount: null,
+        // toUserCount: null,
+        // fromUserNum: null,
+        // toUserNum: null,
+        // createTime: null,
+        // createTime:null,
+        // updateTime: null,
+        timerSend:'',
+        isTimerSend:'2',
+        msgType:'1',
+        // //选择的成员账号
+        // userIdsSelectList:[],
+        // //选择的群聊
+        // groupChatSelectList:[],
+        // //选择的时间
+        // timeScreenCharge:[],
+
+        // //全部客户还是部分客户
+        // groupMsgSelectType:'1',
+
+        // //选择的标签列表
+        // tagsIdsSelectList:[],
+        //
+        // //排除的标签
+        // outTagsIdsSelectList:[],
+
+        imageMediaId: null,
+        imagePicUrl: null,
+        linkTitle: null,
+        linkPicurl: null,
+        linkDesc: null,
+        linkUrl: null,
+        miniprogramTitle: null,
+        miniprogramPicMediaId: null,
+        miniprogramPicUrl: null,
+        miniprogramAppid: null,
+        miniprogramPage: null,
+        fileMediaId: null,
+        fileUrl: null,
+        videoMediaId: null,
+        videoUrl:null,
+      };
+      this.resetForm("form");
+    },
+  }
+};
+</script>
+<style scoped>
+.flex-container > div {
+  width: 100%; /* 确保每个子div宽度充满容器宽度 */
+}
+
+
+</style>