wjj 3 дней назад
Родитель
Сommit
c1711bad81

+ 53 - 0
src/api/aiSipCall/aiSipCallBizGroup.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询aiSIP外呼技能组列表
+export function listAiSipCallBizGroup(query) {
+  return request({
+    url: '/company/aiSipCall/bizGroup/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询aiSIP外呼技能组详细
+export function getAiSipCallBizGroup(groupId) {
+  return request({
+    url: '/company/aiSipCall/bizGroup/' + groupId,
+    method: 'get'
+  })
+}
+
+// 新增aiSIP外呼技能组
+export function addAiSipCallBizGroup(data) {
+  return request({
+    url: '/company/aiSipCall/bizGroup',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改aiSIP外呼技能组
+export function updateAiSipCallBizGroup(data) {
+  return request({
+    url: '/company/aiSipCall/bizGroup',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除aiSIP外呼技能组
+export function delAiSipCallBizGroup(groupId) {
+  return request({
+    url: '/company/aiSipCall/bizGroup/' + groupId,
+    method: 'delete'
+  })
+}
+
+// 导出aiSIP外呼技能组
+export function exportAiSipCallBizGroup(query) {
+  return request({
+    url: '/company/aiSipCall/bizGroup/export',
+    method: 'get',
+    params: query
+  })
+}

+ 62 - 0
src/api/aiSipCall/aiSipCallGateway.js

@@ -0,0 +1,62 @@
+import request from '@/utils/request'
+
+// 查询aiSIP外呼网关列表
+export function listAiSipCallGateway(query) {
+  return request({
+    url: '/company/aiSipCall/gateway/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询aiSIP外呼网关列表
+export function remoteList(data) {
+  return request({
+    url: '/aiSipCall/gateway/remoteList',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询aiSIP外呼网关详细
+export function getAiSipCallGateway(id) {
+  return request({
+    url: '/company/aiSipCall/gateway/' + id,
+    method: 'get'
+  })
+}
+
+// 新增aiSIP外呼网关
+export function addAiSipCallGateway(data) {
+  return request({
+    url: '/company/aiSipCall/gateway',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改aiSIP外呼网关
+export function updateAiSipCallGateway(data) {
+  return request({
+    url: '/company/aiSipCall/gateway',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除aiSIP外呼网关
+export function delAiSipCallGateway(id) {
+  return request({
+    url: '/company/aiSipCall/gateway/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出aiSIP外呼网关
+export function exportAiSipCallGateway(query) {
+  return request({
+    url: '/company/aiSipCall/gateway/export',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/aiSipCall/aiSipCallLlmAgentAccount.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询aiSIP外呼大模型列表
+export function listAiSipCallLlmAgentAccount(query) {
+  return request({
+    url: '/company/aiSipCall/llmAgentAccount/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询aiSIP外呼大模型详细
+export function getAiSipCallLlmAgentAccount(id) {
+  return request({
+    url: '/company/aiSipCall/llmAgentAccount/' + id,
+    method: 'get'
+  })
+}
+
+// 新增aiSIP外呼大模型
+export function addAiSipCallLlmAgentAccount(data) {
+  return request({
+    url: '/company/aiSipCall/llmAgentAccount',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改aiSIP外呼大模型
+export function updateAiSipCallLlmAgentAccount(data) {
+  return request({
+    url: '/company/aiSipCall/llmAgentAccount',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除aiSIP外呼大模型
+export function delAiSipCalllLlmAgentAccount(id) {
+  return request({
+    url: '/company/aiSipCall/llmAgentAccount/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出aiSIP外呼大模型
+export function exportAiSipCallLlmAgentAccount(query) {
+  return request({
+    url: '/company/aiSipCall/llmAgentAccount/export',
+    method: 'get',
+    params: query
+  })
+}

+ 96 - 0
src/api/aiSipCall/aiSipCallOutboundCdr.js

@@ -0,0 +1,96 @@
+import request from '@/utils/request'
+
+// 查询aiSIP手动外呼通话记录列表
+export function listOutboundCdr(query) {
+  return request({
+    url: '/aiSipCall/outboundCdr/list',
+    method: 'get',
+    params: query
+  })
+}
+// 查询远程aiSIP手动外呼通话记录列表
+export function remoteList(data) {
+  return request({
+    url: '/aiSipCall/outboundCdr/remoteList',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询aiSIP手动外呼通话记录详细
+export function getOutboundCdr(id) {
+  return request({
+    url: '/company/aiSipCall/outboundCdr/' + id,
+    method: 'get'
+  })
+}
+
+// 新增aiSIP手动外呼通话记录
+export function addOutboundCdr(data) {
+  return request({
+    url: '/company/aiSipCall/outboundCdr',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改aiSIP手动外呼通话记录
+export function updateOutboundCdr(data) {
+  return request({
+    url: '/company/aiSipCall/outboundCdr',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除aiSIP手动外呼通话记录
+export function delOutboundCdr(id) {
+  return request({
+    url: '/company/aiSipCall/outboundCdr/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出aiSIP手动外呼通话记录
+export function exportOutboundCdr(query) {
+  return request({
+    url: '/aiSipCall/outboundCdr/export',
+    method: 'get',
+    params: query
+  })
+}
+// 取手动外呼客户沟通信息
+export function getCustCommunicationInfo(phoneNum,callType,uuid) {
+  return request({
+    url: '/company/aiSipCall/outboundCdr/getCustCommunicationInfo?phoneNum=' +  phoneNum + "&callType=" + callType + "&uuid=" + uuid,
+    method: 'get'
+  })
+}
+
+// 新增保存手动外呼沟通记录
+export function addCustcallrecord(data) {
+  return request({
+    url: '/company/aiSipCall/outboundCdr/add/custcallrecord',
+    method: 'post',
+    data: data
+  })
+}
+// 手动同步人工外呼通话记录
+export function manualPull() {
+  return request({
+    url: '/company/aiSipCall/outboundCdr/manualPull',
+    method: 'get'
+  })
+}
+
+/**
+ * 根据 uuid 同步通话记录
+ */
+export function syncByUuid(data) {
+    return request({
+        url: '/company/aiSipCall/outboundCdr/syncByUuid',
+        method: 'post',
+        data: data
+    })
+}
+

+ 69 - 0
src/api/aiSipCall/aiSipCallPhone.js

@@ -0,0 +1,69 @@
+import request from '@/utils/request'
+
+// 查询aiSIP外呼通话记录列表
+export function listAiSipCallPhone(query) {
+  return request({
+    url: '/company/aiSipCall/phone/list',
+    method: 'get',
+    params: query
+  })
+}
+// 查询远程aiSIP外呼通话记录列表
+export function remoteList(data) {
+  return request({
+    url: '/company/aiSipCall/phone/remoteList',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询aiSIP外呼通话记录详细
+export function getAiSipCallPhone(id) {
+  return request({
+    url: '/company/aiSipCall/phone/' + id,
+    method: 'get'
+  })
+}
+
+// 新增aiSIP外呼通话记录
+export function addAiSipCallPhone(data) {
+  return request({
+    url: '/company/aiSipCall/phone',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改aiSIP外呼通话记录
+export function updateAiSipCallPhone(data) {
+  return request({
+    url: '/company/aiSipCall/phone',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除aiSIP外呼通话记录
+export function delAiSipCallPhone(id) {
+  return request({
+    url: '/company/aiSipCall/phone/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出aiSIP外呼通话记录
+export function exportAiSipCallPhone(query) {
+  return request({
+    url: '/company/aiSipCall/phone/export',
+    method: 'get',
+    params: query
+  })
+}
+// 同步aiSIP外呼通话记录
+export function manualPull() {
+  return request({
+    url: '/company/aiSipCall/phone/manualPull',
+    method: 'get'
+  })
+}
+

+ 86 - 0
src/api/aiSipCall/aiSipCallTask.js

@@ -0,0 +1,86 @@
+import request from '@/utils/request'
+
+// 查询aiSIP外呼任务列表
+export function listAiSipCallTask(query) {
+  return request({
+    url: '/company/aiSipCall/task/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询aiSIP外呼任务详细
+export function getAiSipCallTask(batchId) {
+  return request({
+    url: '/company/aiSipCall/task/' + batchId,
+    method: 'get'
+  })
+}
+
+// 新增aiSIP外呼任务
+export function addAiSipCallTask(data) {
+  return request({
+    url: '/company/aiSipCall/task',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改aiSIP外呼任务
+export function updateAiSipCallTask(data) {
+  return request({
+    url: '/company/aiSipCall/task',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除aiSIP外呼任务
+export function delAiSipCallTask(batchId) {
+  return request({
+    url: '/company/aiSipCall/task/' + batchId,
+    method: 'delete'
+  })
+}
+
+// 导出aiSIP外呼任务
+export function exportAiSipCallTask(query) {
+  return request({
+    url: '/company/aiSipCall/task/export',
+    method: 'get',
+    params: query
+  })
+}
+// 启动aiSIP外呼任务
+export function startTask(batchId) {
+  return request({
+    url: '/company/aiSipCall/task/startTask/' + batchId,
+    method: 'post'
+  })
+}
+// 暂停aiSIP外呼任务
+export function stopTask(batchId) {
+  return request({
+    url: '/company/aiSipCall/task/stopTask/' + batchId,
+    method: 'post'
+  })
+}
+// 公共导入数据
+export function commonImportExcel(data) {
+  return request({
+    url: '/company/aiSipCall/task/common/importExcel',
+    method: 'post',
+    data: data,
+    headers: {
+      'Content-Type': undefined
+    }
+  })
+}
+// 下载aiSIP外呼模板
+export function downloadTemplateByType(type) {
+  return request({
+    url: '/company/aiSipCall/task/download/template/' + type,
+    method: 'get',
+    responseType: 'blob'
+  })
+}

+ 77 - 0
src/api/aiSipCall/aiSipCallUser.js

@@ -0,0 +1,77 @@
+import request from '@/utils/request'
+
+// 查询sip用户信息列表
+export function listAiSipCallUser(query) {
+  return request({
+    url: '/aiSipCall/aiSipCallUser/list',
+    method: 'get',
+    params: query
+  })
+}
+// 查询sip用户信息列表
+export function myCallUser(query) {
+  return request({
+    url: '/company/aiSipCall/aiSipCallUser/myCallUser',
+    method: 'get',
+    params: query
+  })
+}
+// 查询sip用户信息详细
+export function getAiSipCallUser(userId) {
+  return request({
+    url: '/company/aiSipCall/aiSipCallUser/' + userId,
+    method: 'get'
+  })
+}
+
+// 新增sip用户信息
+export function addAiSipCallUser(data) {
+  return request({
+    url: '/company/aiSipCall/aiSipCallUser',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改sip用户信息
+export function updateAiSipCallUser(data) {
+  return request({
+    url: '/company/aiSipCall/aiSipCallUser',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除sip用户信息
+export function delAiSipCallUser(userId) {
+  return request({
+    url: '/company/aiSipCall/aiSipCallUser/' + userId,
+    method: 'delete'
+  })
+}
+
+// 导出sip用户信息
+export function exportAiSipCallUser(query) {
+  return request({
+    url: '/aiSipCall/aiSipCallUser/export',
+    method: 'get',
+    params: query
+  })
+}
+// 获取未绑定的分机列表
+export function getUnBindExtnum(query) {
+  return request({
+    url: '/company/aiSipCall/aiSipCallUser/getUnBindExtnum',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询aiSIP工具条基础配置参数
+export function getToolbarBasicParam(data) {
+  return request({
+    url: '/company/aiSipCall/aiSipCallUser/getToolbarBasicParam',
+    method: 'post',
+    data: data
+  })
+}

+ 53 - 0
src/api/aiSipCall/aiSipCallVoiceTtsAliyun.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询aiSIP外呼阿里云音色列表
+export function listAiSipCallVoiceTtsAliyun(query) {
+  return request({
+    url: '/company/aiSipCall/voiceTtsAliyun/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询aiSIP外呼阿里云音色详细
+export function getAiSipCallVoiceTtsAliyun(id) {
+  return request({
+    url: '/company/aiSipCall/voiceTtsAliyun/' + id,
+    method: 'get'
+  })
+}
+
+// 新增aiSIP外呼阿里云音色
+export function addAiSipCallVoiceTtsAliyun(data) {
+  return request({
+    url: '/company/aiSipCall/voiceTtsAliyun',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改aiSIP外呼阿里云音色
+export function updateAiSipCallVoiceTtsAliyun(data) {
+  return request({
+    url: '/company/aiSipCall/voiceTtsAliyun',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除aiSIP外呼阿里云音色
+export function delAiSipCallVoiceTtsAliyun(id) {
+  return request({
+    url: '/company/aiSipCall/voiceTtsAliyun/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出aiSIP外呼阿里云音色
+export function exportAiSipCallVoiceTtsAliyun(query) {
+  return request({
+    url: '/company/aiSipCall/voiceTtsAliyun/export',
+    method: 'get',
+    params: query
+  })
+}

+ 392 - 0
src/views/aiSipCall/aiSipCallOutboundCar.vue

@@ -0,0 +1,392 @@
+音视频类型<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="86px" class="search-form">
+      <el-form-item label="通话 UUID" prop="uuid">
+        <el-input
+          v-model="queryParams.uuid"
+          placeholder="请输入通话 UUID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+          style="width: 200px"
+        />
+      </el-form-item>
+      <el-form-item label="被叫号码" prop="callee">
+        <el-input
+          v-model="queryParams.callee"
+          placeholder="请输入被叫号码"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+          style="width: 150px"
+        />
+      </el-form-item>
+      <el-form-item label="工号" prop="opnum">
+        <el-input
+          v-model="queryParams.opnum"
+          placeholder="请输入工号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+          style="width: 150px"
+        />
+      </el-form-item>
+      <el-form-item label="通话分钟">
+        <div class="time-range">
+          <el-input-number v-model="queryParams.timeLenStart" :min="0" placeholder="最小值" size="small"
+                           style="width: 130px"/>
+          <span class="range-separator">-</span>
+          <el-input-number v-model="queryParams.timeLenEnd" :min="0" placeholder="最大值" size="small"
+                           style="width: 130px"/>
+        </div>
+      </el-form-item>
+      <el-form-item label="外呼时间">
+        <div class="time-range">
+          <el-date-picker v-model="queryParams.startTimeStart" size="small" style="width: 190px"
+                          value-format="yyyy-MM-dd HH:mm:ss" type="datetime" placeholder="开始"/>
+          <span class="range-separator">-</span>
+          <el-date-picker v-model="queryParams.startTimeEnd" size="small" style="width: 190px"
+                          value-format="yyyy-MM-dd HH:mm:ss" type="datetime" placeholder="结束"/>
+        </div>
+      </el-form-item>
+      <el-form-item label="接听时间">
+        <div class="time-range">
+          <el-date-picker v-model="queryParams.answeredTimeStart" size="small" style="width: 190px"
+                          value-format="yyyy-MM-dd HH:mm:ss" type="datetime" placeholder="开始"/>
+          <span class="range-separator">-</span>
+          <el-date-picker v-model="queryParams.answeredTimeEnd" size="small" style="width: 190px"
+                          value-format="yyyy-MM-dd HH:mm:ss" type="datetime" placeholder="结束"/>
+        </div>
+      </el-form-item>
+      <el-form-item label="挂机时间">
+        <div class="time-range">
+          <el-date-picker v-model="queryParams.endTimeStart" size="small" style="width: 190px"
+                          value-format="yyyy-MM-dd HH:mm:ss" type="datetime" placeholder="开始"/>
+          <span class="range-separator">-</span>
+          <el-date-picker v-model="queryParams.endTimeEnd" size="small" style="width: 190px"
+                          value-format="yyyy-MM-dd HH:mm:ss" type="datetime" placeholder="结束"/>
+        </div>
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="outboundCdrList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="通话UUID" align="center" prop="uuid" />
+      <el-table-column label="音视频类型" align="center" prop="callType">
+        <template slot-scope="scope">
+          <span v-if="scope.row.callType === 'audio'">音频</span>
+          <span v-else-if="scope.row.callType === 'video'">视频</span>
+          <span v-else>{{ scope.row.callType }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="工号" align="center" prop="opnum" />
+      <el-table-column label="被叫号码" align="center" prop="callee" />
+      <el-table-column label="外呼时间" align="center" prop="startTimeStr" />
+      <el-table-column label="接听时间" align="center" prop="answeredTimeStr" />
+      <el-table-column label="挂断时间" align="center" prop="endTimeStr" />
+      <el-table-column label="通话时长" align="center" prop="timeLenSec" />
+      <el-table-column label="纯通时长" align="center" prop="timeLenValidStr" />
+      <el-table-column label="挂断原因" align="center" prop="hangupCause" />
+      <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-video-play"
+            @click="playVoice(scope.row.wavFileUrl)"
+            v-hasPermi="['aiSipCall:phone:downloadVoice']"
+            v-if="scope.row.wavFileUrl"
+            style="color: #409EFF;"
+          >播放
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-download"
+            @click="downloadVoice(scope.row.wavFileUrl)"
+            v-hasPermi="['aiSipCall:phone:downloadVoice']"
+            v-if="scope.row.wavFileUrl"
+            style="color: #67C23A;"
+          >下载
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import { exportOutboundCdr,remoteList } from "@/api/aiSipCall/aiSipCallOutboundCdr";
+
+export default {
+  name: "OutboundCdr",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // aiSIP手动外呼通话记录表格数据
+      outboundCdrList: [],
+      // 弹出层标题
+      title: "",
+      // 当前播放的音频对象
+      currentAudio: null,
+      // 音频服务器基础 URL
+      AUDIO_BASE_URL: 'http://129.28.164.235:8899',
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        timeLenStart: undefined,
+        timeLenEnd: undefined,
+        callType: '03',
+        caller: null,
+        opnum: null,
+        startTimeStart: null,
+        startTimeEnd: null,
+        answeredTimeStart: null,
+        answeredTimeEnd: null,
+        endTimeStart: this.getTodayStart(),
+        endTimeEnd: null,
+        callee: null,
+        startTime: null,
+        answeredTime: null,
+        endTime: null,
+        uuid: null,
+        timeLen: null,
+        timeLenValid: null,
+        recordFilename: null,
+        chatContent: null,
+        hangupCause: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {}
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    // 获取今天开始时间(yyyy-MM-dd 00:00:00)
+    getTodayStart() {
+      const now = new Date();
+      return now.getFullYear() + '-' +
+        String(now.getMonth() + 1).padStart(2, '0') + '-' +
+        String(now.getDate()).padStart(2, '0') + ' 00:00:00';
+    },
+    /** 查询aiSIP手动外呼通话记录列表 */
+    getList() {
+      this.loading = true;
+      remoteList(this.queryParams).then(response => {
+        this.outboundCdrList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        pageNum: 1,
+        pageSize: 10,
+        callType: '03',
+        caller: null,
+        opnum: null,
+        timeLenStart: null,
+        timeLenEnd: null,
+        startTimeStart: null,
+        startTimeEnd: null,
+        answeredTimeStart: null,
+        answeredTimeEnd: null,
+        endTimeStart: this.getTodayStart(),
+        endTimeEnd: null,
+        callee: null,
+        startTime: null,
+        answeredTime: null,
+        endTime: null,
+        uuid: null,
+        timeLen: null,
+        timeLenValid: null,
+        recordFilename: null,
+        chatContent: null,
+        hangupCause: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.queryParams.startTimeStart = null;
+      this.queryParams.startTimeEnd = null;
+      this.queryParams.timeLenStart = undefined;
+      this.queryParams.timeLenEnd = undefined;
+      this.queryParams.answeredTimeStart = null;
+      this.queryParams.answeredTimeEnd = null;
+      this.queryParams.endTimeStart = this.getTodayStart();
+      this.queryParams.endTimeEnd = null;
+      this.queryParams.uuid = null;
+      this.queryParams.callee = null;
+      this.queryParams.opnum = null;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+
+  
+  
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有aiSIP手动外呼通话记录数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportOutboundCdr(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
+    },
+
+
+    /** 构建完整的音频 URL */
+    buildAudioUrl(recordFilename) {
+      if (!recordFilename) return '';
+      return recordFilename.startsWith('http') ? recordFilename : this.AUDIO_BASE_URL + recordFilename;
+    },
+    /** 获取文件名 */
+    getFileName(url) {
+      if (!url) return 'audio.wav';
+      return url.split('/').pop() || 'audio.wav';
+    },
+    /** 播放语音文件 */
+    playVoice(recordFilename) {
+      // 非空校验
+      if (!recordFilename) {
+        this.$message.error('录音文件不存在');
+        return;
+      }
+
+      // 如果已有音频在播放,先完全清理
+      if (this.currentAudio) {
+        this.currentAudio.pause();
+        this.currentAudio.src = '';
+        this.currentAudio.onloadedmetadata = null;
+        this.currentAudio.onerror = null;
+        this.currentAudio.onended = null;
+        this.currentAudio = null;
+      }
+
+      // 创建新的 Audio 对象
+      const audio = new Audio();
+      this.currentAudio = audio;
+
+      // 构建音频 URL
+      audio.src = this.buildAudioUrl(recordFilename);
+
+      // 设置音频类型为 wav
+      audio.type = 'audio/wav';
+
+      audio.onloadedmetadata = () => {
+        console.log('音频加载成功,时长:', audio.duration, '秒');
+      };
+
+      audio.play().catch(err => {
+        // 只在非用户主动切换的情况下显示错误提示
+        if (this.currentAudio === audio) {
+          this.$message.error('音频播放失败:' + (err.message || '请检查网络或文件格式'));
+        }else{
+          this.$message.error('音频播放失败:' + err);
+        }
+      });
+
+      // 播放结束后清理
+      audio.onended = () => {
+        this.currentAudio = null;
+      };
+
+      // 错误处理
+      audio.onerror = (err) => {
+        // 只在当前音频对象仍然是这个时才显示错误(避免切换时的误报)
+        if (this.currentAudio === audio) {
+          this.$message.error('无法播放录音:' + (err.message || '未知错误'));
+          this.currentAudio = null;
+        }else{
+          this.$message.error('音频加载失败:' + err);
+        }
+      };
+    },
+    /** 下载语音文件 */
+    downloadVoice(recordFilename) {
+      // 非空校验
+      if (!recordFilename) {
+        this.$message.error('录音文件不存在');
+        return;
+      }
+
+      // 构建完整的音频 URL
+      const audioUrl = this.buildAudioUrl(recordFilename);
+
+      // 创建临时下载链接
+      const link = document.createElement('a');
+      link.href = audioUrl;
+      link.setAttribute('download', this.getFileName(audioUrl));
+      link.target = '_blank'; // 在新窗口打开
+
+      // 添加到 DOM 并触发点击
+      document.body.appendChild(link);
+      link.click();
+
+      // 清理 DOM
+      document.body.removeChild(link);
+    },
+  }
+};
+</script>

+ 278 - 0
src/views/aiSipCall/aiSipCallUser.vue

@@ -0,0 +1,278 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+
+      <el-form-item label="登录账号" prop="loginName">
+        <el-input
+          v-model="queryParams.loginName"
+          placeholder="请输入登录账号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="手机号码" prop="phonenumber">
+        <el-input
+          v-model="queryParams.phonenumber"
+          placeholder="请输入手机号码"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="用户昵称" prop="userName">
+        <el-input
+          v-model="queryParams.userName"
+          placeholder="请输入用户昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="用户状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择用户状态" clearable size="small">
+          <el-option label="全部" value=""/>
+          <el-option label="正常" value="0"/>
+          <el-option label="停用" value="1"/>
+        </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="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['aiSipCallUser:aiSipCallUser:export']"
+        >导出
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="aiSipCallUserList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center"/>
+      <el-table-column label="登录账号" align="center" prop="loginName"/>
+      <el-table-column label="用户姓名" align="center" prop="userName"/>
+      <el-table-column label="手机号码" align="center" prop="phonenumber"/>
+      <el-table-column label="用户性别" align="center" prop="sex">
+        <template slot-scope="scope">
+          <span v-if="scope.row.sex == '0'">男</span>
+          <span v-else-if="scope.row.sex == '1'">女</span>
+          <span v-else-if="scope.row.sex == '2'">未知</span>
+          <span v-else>{{ scope.row.sex }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="用户状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <span v-if="scope.row.status == '0'">正常</span>
+          <span v-else-if="scope.row.status == '1'">停用</span>
+          <span v-else>{{ scope.row.status }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="绑定分机号" align="center" prop="extNum"/>
+      <el-table-column label="绑定网关" align="center" prop="gatewayIds">
+        <template slot-scope="scope">
+          <span>{{ getGatewayNames(scope.row.gatewayIds) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark"/>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180"/>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </div>
+</template>
+
+<script>
+import {
+  listAiSipCallUser,
+  exportAiSipCallUser,
+} from "@/api/aiSipCall/aiSipCallUser";
+import {remoteList} from "../../api/aiSipCall/aiSipCallGateway";
+
+export default {
+  name: "AiSipCallUser",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // sip用户信息表格数据
+      aiSipCallUserList: [],
+      // 分机号码下拉选项
+      extNumOptions: [],
+      //网关列表
+      gateways: [],
+      // 选中的网关
+      selectedGateways: [],
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        deptId: null,
+        loginName: null,
+        userName: null,
+        userType: null,
+        email: null,
+        phonenumber: null,
+        sex: null,
+        avatar: null,
+        password: null,
+        salt: null,
+        status: null,
+        loginIp: null,
+        loginDate: null,
+        pwdUpdateDate: null,
+        logo: null,
+        companyUserId: null,
+        extNum: null
+      },
+      // 表单参数
+      form: {
+        password: '123456'
+      },
+      // 是否显示密码明文
+      passwordVisible: false,
+      // 弹窗标题
+      title: "",
+      // 表单校验
+      rules: {
+        loginName: [
+          {required: true, message: "登录账号不能为空", trigger: "blur"}
+        ],
+        password: [
+          {required: true, message: "登录密码不能为空", trigger: "blur"}
+        ],
+        userName: [
+          {required: true, message: "用户姓名不能为空", trigger: "blur"}
+        ],
+        extNum: [
+          {required: true, message: "绑定的分机号不能为空", trigger: "change"}
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询 sip 用户信息列表 */
+    getList() {
+      this.loading = true;
+      // 先获取网关列表
+      remoteList({ pageNum:1, pageSize: 500,params:{purposes: [1, 3]} }).then(response => {
+        this.gateways = response.rows || [];
+        // 再获取用户列表
+        listAiSipCallUser(this.queryParams).then(response => {
+          this.aiSipCallUserList = response.rows;
+          this.total = response.total;
+          this.loading = false;
+        });
+      }).catch(() => {
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        password: '123456'
+      };
+      this.selectedGateways = [];
+      this.passwordVisible = false;
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.userId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加 sip用户信息";
+    },
+ 
+    // 切换密码显示/隐藏
+    togglePasswordVisible() {
+      this.passwordVisible = !this.passwordVisible;
+    },
+    
+    
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有 sip 用户信息数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportAiSipCallUser(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+      })
+    },
+    /** 根据网关 IDs 获取网关名称 */
+    getGatewayNames(gatewayIds) {
+      if (!gatewayIds) {
+        return '';
+      }
+      const ids = gatewayIds.split(',').map(id => Number(id));
+      const names = ids
+        .map(id => {
+          const gateway = this.gateways.find(gw => gw.id === id);
+          return gateway ? gateway.gwDesc : '';
+        })
+        .filter(name => name);
+      return names.join(', ');
+    }
+  }
+};
+</script>