aiSipCallTask.vue 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. <template>
  2. <div class="app-container">
  3. <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
  4. <el-form-item label="任务名称" prop="batchName">
  5. <el-input
  6. v-model="queryParams.batchName"
  7. placeholder="请输入任务名称"
  8. clearable
  9. size="small"
  10. @keyup.enter.native="handleQuery"
  11. />
  12. </el-form-item>
  13. <el-form-item label="任务类型" prop="taskType">
  14. <el-select v-model="queryParams.taskType" placeholder="请选择任务类型" clearable size="small">
  15. <el-option label="全部" value="" />
  16. <el-option label="纯人工预测外呼" value="0" />
  17. <el-option label="AI外呼" value="1" />
  18. <el-option label="语音通知" value="2" />
  19. </el-select>
  20. </el-form-item>
  21. <el-form-item label="任务状态" prop="ifcall">
  22. <el-select v-model="queryParams.ifcall" placeholder="请选择任务状态" clearable size="small">
  23. <el-option label="全部" value="" />
  24. <el-option label="外呼中" value="1" />
  25. <el-option label="停止外呼" value="0" />
  26. </el-select>
  27. </el-form-item>
  28. <el-form-item label="创建时间">
  29. <el-date-picker v-model="queryParams.createTimeStart" size="small" style="width: 180px"
  30. value-format="yyyy-MM-dd HH:mm:ss" type="datetime" placeholder="开始时间"/>
  31. <span style="margin: 0 8px">-</span>
  32. <el-date-picker v-model="queryParams.createTimeEnd" size="small" style="width: 180px"
  33. value-format="yyyy-MM-dd HH:mm:ss" type="datetime" placeholder="结束时间"/>
  34. </el-form-item>
  35. <el-form-item label="外呼线路" prop="gatewayId">
  36. <el-select v-model="queryParams.gatewayId" placeholder="请选择外呼线路" clearable size="small">
  37. <el-option label="全部" value="" />
  38. <el-option
  39. v-for="item in gatewayList"
  40. :key="item.id"
  41. :label="item.gwName"
  42. :value="item.id"
  43. />
  44. </el-select>
  45. </el-form-item>
  46. <el-form-item label="音色" prop="voiceSource">
  47. <el-select v-model="queryParams.voiceSource" placeholder="请选择音色" clearable size="small">
  48. <el-option label="全部" value="" />
  49. <el-option
  50. v-for="(item, index) in voiceSourceList"
  51. :key="'query-' + item.voiceCode + '-' + index"
  52. :label="item.voiceName"
  53. :value="item.voiceCode"
  54. />
  55. </el-select>
  56. </el-form-item>
  57. <el-form-item>
  58. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  59. <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  60. </el-form-item>
  61. </el-form>
  62. <el-row :gutter="10" class="mb8">
  63. <el-col :span="1.5">
  64. <el-button
  65. type="primary"
  66. plain
  67. icon="el-icon-plus"
  68. size="mini"
  69. @click="handleAdd"
  70. v-hasPermi="['company:aiSipCall:task:add']"
  71. >新增</el-button>
  72. </el-col>
  73. <el-col :span="1.5">
  74. <el-button
  75. type="primary"
  76. plain
  77. icon="el-icon-plus"
  78. size="mini"
  79. @click="downloadCallTemplate"
  80. v-hasPermi="['company:aiSipCall:task:download:template']"
  81. >下载外呼模板</el-button>
  82. </el-col>
  83. <!-- <el-col :span="1.5">-->
  84. <!-- <el-button-->
  85. <!-- type="success"-->
  86. <!-- plain-->
  87. <!-- icon="el-icon-edit"-->
  88. <!-- size="mini"-->
  89. <!-- :disabled="single"-->
  90. <!-- @click="handleUpdate"-->
  91. <!-- v-hasPermi="['company:aiSipCall:task:edit']"-->
  92. <!-- >修改</el-button>-->
  93. <!-- </el-col>-->
  94. <!-- <el-col :span="1.5">-->
  95. <!-- <el-button-->
  96. <!-- type="danger"-->
  97. <!-- plain-->
  98. <!-- icon="el-icon-delete"-->
  99. <!-- size="mini"-->
  100. <!-- :disabled="multiple"-->
  101. <!-- @click="handleDelete"-->
  102. <!-- v-hasPermi="['company:aiSipCall:task:remove']"-->
  103. <!-- >删除</el-button>-->
  104. <!-- </el-col>-->
  105. <el-col :span="1.5">
  106. <el-button
  107. type="warning"
  108. plain
  109. icon="el-icon-download"
  110. size="mini"
  111. :loading="exportLoading"
  112. @click="handleExport"
  113. v-hasPermi="['company:aiSipCall:task:export']"
  114. >导出</el-button>
  115. </el-col>
  116. <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  117. </el-row>
  118. <el-table border v-loading="loading" :data="aiSipCallList" @selection-change="handleSelectionChange">
  119. <el-table-column type="selection" width="55" align="center" />
  120. <el-table-column label="任务ID" align="center" prop="batchId" />
  121. <el-table-column label="任务名称" align="center" prop="batchName" />
  122. <el-table-column label="任务类型" align="center" prop="taskType">
  123. <template slot-scope="scope">
  124. <span v-if="scope.row.taskType === '0'">纯人工预测外呼</span>
  125. <span v-else-if="scope.row.taskType === 1">AI外呼</span>
  126. <span v-else-if="scope.row.taskType === 2">语音通知</span>
  127. <span v-else>{{ scope.row.taskType }}</span>
  128. </template>
  129. </el-table-column>
  130. <el-table-column label="外呼状态" align="center" prop="ifcall" >
  131. <template slot-scope="scope">
  132. <span v-if="scope.row.ifcall === 0">停止外呼</span>
  133. <span v-else-if="scope.row.ifcall === 1">外呼中</span>
  134. <span v-else>{{ scope.row.ifcall }}</span>
  135. </template>
  136. </el-table-column>
  137. <el-table-column label="最大并发" align="center" prop="threadNum" />
  138. <el-table-column label="线路名称" align="center" prop="gatewayId">
  139. <template slot-scope="scope">
  140. <span v-for="item in gatewayList" v-if="scope.row.gatewayId === item.id">{{ item.gwName }}</span>
  141. </template>
  142. </el-table-column>
  143. <el-table-column label="音色" align="center" prop="voiceCode">
  144. <template slot-scope="scope">
  145. <span v-for="(item, index) in voiceSourceList" v-if="scope.row.voiceCode === item.voiceCode" :key="'table-' + item.voiceCode + '-' + index">{{ item.voiceName }}</span>
  146. </template>
  147. </el-table-column>
  148. <el-table-column label="创建时间" align="center" prop="createTime" />
  149. <!-- <el-table-column label="总名单量" align="center" prop="phoneCount" />-->
  150. <!-- <el-table-column label="未拨打" align="center" prop="noCallCount" />-->
  151. <!-- <el-table-column label="已拨打" align="center" prop="callCount" />-->
  152. <!-- <el-table-column label="接通量" align="center" prop="connectCount" />-->
  153. <!-- <el-table-column label="接通率" align="center" prop="realConnectRate" />-->
  154. <el-table-column label="远程任务ID" align="center" prop="remoteBatchId" />
  155. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  156. <template slot-scope="scope">
  157. <!-- <el-button-->
  158. <!-- size="mini"-->
  159. <!-- type="text"-->
  160. <!-- icon="el-icon-edit"-->
  161. <!-- @click="handleUpdate(scope.row)"-->
  162. <!-- v-hasPermi="['company:aiSipCall:task:edit']"-->
  163. <!-- >修改</el-button>-->
  164. <el-button
  165. size="mini"
  166. type="text"
  167. icon="el-icon-video-play"
  168. @click="startTask(scope.row.batchId)"
  169. v-hasPermi="['company:aiSipCall:task:startTask']"
  170. >启动外呼</el-button>
  171. <el-button
  172. size="mini"
  173. type="text"
  174. icon="el-icon-video-pause"
  175. @click="stopTask(scope.row.batchId)"
  176. v-hasPermi="['company:aiSipCall:task:stopTask']"
  177. >停止外呼</el-button>
  178. <el-button
  179. size="mini"
  180. type="text"
  181. icon="el-icon-upload"
  182. @click="handleImportData(scope.row.batchId)"
  183. v-hasPermi="['company:aiSipCall:task:commonImportExcel']"
  184. >导入外呼模板</el-button>
  185. <!-- <el-button-->
  186. <!-- size="mini"-->
  187. <!-- type="text"-->
  188. <!-- icon="el-icon-delete"-->
  189. <!-- @click="handleDelete(scope.row)"-->
  190. <!-- v-hasPermi="['company:aiSipCall:task:remove']"-->
  191. <!-- >删除</el-button>-->
  192. </template>
  193. </el-table-column>
  194. </el-table>
  195. <pagination
  196. v-show="total>0"
  197. :total="total"
  198. :page.sync="queryParams.pageNum"
  199. :limit.sync="queryParams.pageSize"
  200. @pagination="getList"
  201. />
  202. <!-- 添加或修改aiSIP外呼任务对话框 -->
  203. <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
  204. <el-form ref="form" :model="form" :rules="rules" label-width="80px">
  205. <el-form-item label="任务名称" prop="batchName">
  206. <el-input v-model="form.batchName" placeholder="请输入外呼任务的业务组 " />
  207. </el-form-item>
  208. <el-form-item label="任务类型" prop="taskType">
  209. <el-select v-model="form.taskType" placeholder="请选择任务类型" clearable>
  210. <!-- <el-option label="纯人工预测外呼" value="0" />-->
  211. <el-option label="AI外呼" value="1" />
  212. <!-- <el-option label="语音通知" value="2" />-->
  213. <!-- <el-option label="IVR外呼" value="3" />-->
  214. </el-select>
  215. </el-form-item>
  216. <!-- 纯人工预测外呼显示项 -->
  217. <el-form-item label="预估接通率(%)" prop="conntectRate" v-show="form.taskType === '0'">
  218. <el-input v-model="form.conntectRate" placeholder="请输入预估接通率" />
  219. </el-form-item>
  220. <el-form-item label="平均振铃时长(秒)" prop="avgRingTimeLen" v-show="form.taskType === '0'">
  221. <el-input v-model="form.avgRingTimeLen" placeholder="请输入平均振铃时长" />
  222. </el-form-item>
  223. <el-form-item label="平均通话时长(秒)" prop="avgCallTalkTimeLen" v-show="form.taskType === '0'">
  224. <el-input v-model="form.avgCallTalkTimeLen" placeholder="请输入平均通话时长" />
  225. </el-form-item>
  226. <el-form-item label="平均事后处理时长(秒)" prop="avgCallEndProcessTimeLen" v-show="form.taskType === '0'">
  227. <el-input v-model="form.avgCallEndProcessTimeLen" placeholder="请输入平均事后处理时长" />
  228. </el-form-item>
  229. <el-form-item label="最大并发" prop="threadNum">
  230. <el-input v-model="form.threadNum" placeholder="请输入最大并发" />
  231. </el-form-item>
  232. <el-form-item label="外呼线路" prop="gatewayId">
  233. <el-select v-model="form.gatewayId" placeholder="请选择外呼线路" clearable>
  234. <el-option
  235. v-for="item in gatewayList"
  236. :key="item.id"
  237. :label="item.gwName"
  238. :value="item.id"
  239. />
  240. </el-select>
  241. </el-form-item>
  242. <el-form-item label="自动停止" prop="autoStop">
  243. <el-select v-model="form.autoStop" placeholder="请选择自动停止" clearable>
  244. <el-option label="名单拨打完后任务自动停止" value="1" />
  245. <el-option label="任务不自动停止" value="0" />
  246. </el-select>
  247. </el-form-item>
  248. <el-form-item label="大模型底座" prop="llmAccountId" v-show="form.taskType === '1'">
  249. <el-select v-model="form.llmAccountId" placeholder="请选择大模型底座" clearable>
  250. <el-option
  251. v-for="item in llmAgentAccountList"
  252. :key="item.id"
  253. :label="item.name"
  254. :value="item.id"
  255. />
  256. </el-select>
  257. </el-form-item>
  258. <el-form-item label="TTS厂商" prop="voiceSource" v-show="form.taskType === '1' || form.taskType === '2'">
  259. <el-select v-model="form.voiceSource" placeholder="请选择TTS厂商" clearable @change="handleVoiceSourceChange">
  260. <el-option label="电信" value="chinatelecom_tts" />
  261. <el-option label="豆包" value="doubao_vcl_tts" />
  262. <el-option label="阿里云" value="aliyun_tts" />
  263. </el-select>
  264. </el-form-item>
  265. <el-form-item label="音色" prop="voiceCode" v-show="form.voiceSource">
  266. <el-select v-model="form.voiceCode" placeholder="请选择音色" clearable>
  267. <el-option
  268. v-for="(item, index) in getCurrentVoiceCodeList()"
  269. :key="'form-' + item.voiceCode + '-' + index"
  270. :label="item.voiceName"
  271. :value="item.voiceCode"
  272. />
  273. </el-select>
  274. </el-form-item>
  275. <el-form-item label="ASR厂商" prop="asrProvider" v-show="form.taskType === '1' || form.taskType === '2'">
  276. <el-select v-model="form.asrProvider" placeholder="请选择 ASR 厂商" clearable>
  277. <el-option label="电信" value="chinatelecom" />
  278. <el-option label="FunASR" value="funasr" />
  279. <el-option label="阿里云" value="aliyun" />
  280. </el-select>
  281. </el-form-item>
  282. <el-form-item label="播放次数" prop="playTimes" v-show="form.taskType === '2'">
  283. <el-input v-model="form.playTimes" placeholder="请输入播放次数" />
  284. </el-form-item>
  285. <el-form-item label="转人工方式" prop="aiTransferType" v-show="form.taskType === '1'">
  286. <el-select v-model="form.aiTransferType" placeholder="请选择转人工方式" clearable @change="handleAiTransferTypeChange">
  287. <el-option label="电话排队(需要电话工具条)" value="acd" />
  288. <el-option label="转分机(无需电话工具条)" value="extension" />
  289. <el-option label="转外部网关" value="gateway" />
  290. </el-select>
  291. </el-form-item>
  292. <el-form-item label="业务组" prop="aiTransferGroupId" v-show="form.taskType === '1' && form.aiTransferType !== 'extension' && form.aiTransferType !== 'gateway'">
  293. <el-select v-model="form.aiTransferGroupId" placeholder="请选择业务组" clearable size="small">
  294. <el-option label="请选择业务组" value="" />
  295. <el-option
  296. v-for="item in bizGroupList"
  297. :key="item.groupId"
  298. :label="item.bizGroupName"
  299. :value="item.groupId"
  300. />
  301. </el-select>
  302. </el-form-item>
  303. <el-form-item label="转接分机(多个用空格分割)" prop="aiTransferExtNumber" v-show="form.aiTransferType === 'extension'">
  304. <el-input v-model="form.aiTransferExtNumber" placeholder="请输入转接分机" />
  305. </el-form-item>
  306. <el-form-item label="网关" prop="aiTransferGatewayId" v-show="form.aiTransferType === 'gateway'">
  307. <el-select v-model="form.aiTransferGatewayId" placeholder="请选择外呼线路" clearable>
  308. <el-option
  309. v-for="item in gatewayList"
  310. :key="item.id"
  311. :label="item.gwName"
  312. :value="item.id"
  313. />
  314. </el-select>
  315. </el-form-item>
  316. <el-form-item label="转接号码" prop="aiTransferGatewayDestNumber" v-show="form.aiTransferType === 'gateway'">
  317. <el-input v-model="form.aiTransferGatewayDestNumber" placeholder="请输入转接号码" />
  318. </el-form-item>
  319. </el-form>
  320. <div slot="footer" class="dialog-footer">
  321. <el-button type="primary" @click="submitForm">确 定</el-button>
  322. <el-button @click="cancel">取 消</el-button>
  323. </div>
  324. </el-dialog>
  325. <!-- 导入数据对话框 -->
  326. <el-dialog :title="uploadTitle" :visible.sync="uploadOpen" width="500px" append-to-body>
  327. <el-form ref="uploadForm" :model="uploadForm">
  328. <el-form-item label="选择文件">
  329. <input type="file" ref="fileInput" @change="handleFileChange" accept=".xlsx,.xls,.csv" style="width: 100%;">
  330. </el-form-item>
  331. </el-form>
  332. <div slot="footer" class="dialog-footer">
  333. <el-button @click="uploadOpen = false">取 消</el-button>
  334. </div>
  335. </el-dialog>
  336. </div>
  337. </template>
  338. <script>
  339. import { downloadTemplateByType,listAiSipCallTask, getAiSipCallTask, delAiSipCallTask, addAiSipCallTask, updateAiSipCallTask, exportAiSipCallTask, startTask, stopTask,commonImportExcel } from "@/api/aiSipCall/aiSipCallTask";
  340. import {listAiSipCallGateway} from "../../api/aiSipCall/aiSipCallGateway";
  341. import {listAiSipCallVoiceTtsAliyun} from "../../api/aiSipCall/aiSipCallVoiceTtsAliyun";
  342. import {listAiSipCallLlmAgentAccount } from "@/api/aiSipCall/aiSipCallLlmAgentAccount";
  343. import { listAiSipCallBizGroup } from "@/api/aiSipCall/aiSipCallBizGroup";
  344. export default {
  345. name: "aiSipCallTask",
  346. data() {
  347. return {
  348. // 遮罩层
  349. loading: true,
  350. // 导出遮罩层
  351. exportLoading: false,
  352. gatewayList: [],
  353. voiceSourceList: [],
  354. // 按厂商分类的音色列表
  355. voiceCodeLists: {
  356. chinatelecom_tts: [],
  357. doubao_vcl_tts: [],
  358. aliyun_tts: []
  359. },
  360. llmAgentAccountList: [],
  361. bizGroupList: [],
  362. // 选中数组
  363. ids: [],
  364. // 非单个禁用
  365. single: true,
  366. // 非多个禁用
  367. multiple: true,
  368. // 显示搜索条件
  369. showSearch: true,
  370. // 总条数
  371. total: 0,
  372. // aiSIP 外呼任务表格数据
  373. aiSipCallList: [],
  374. // 弹出层标题
  375. title: "",
  376. // 是否显示弹出层
  377. open: false,
  378. // 上传对话框标题
  379. uploadTitle: "导入数据",
  380. // 是否显示上传对话框
  381. uploadOpen: false,
  382. // 上传表单
  383. uploadForm: {
  384. batchId: null,
  385. file: null
  386. },
  387. // 当前选中的任务行
  388. currentRow: null,
  389. // 查询参数
  390. queryParams: {
  391. pageNum: 1,
  392. pageSize: 10,
  393. createTimeStart: null,
  394. createTimeEnd: null,
  395. groupId: null,
  396. batchName: null,
  397. ifcall: null,
  398. rate: null,
  399. threadNum: null,
  400. createTime: null,
  401. executing: null,
  402. stopTime: null,
  403. userid: null,
  404. taskType: null,
  405. gatewayId: null,
  406. voiceCode: null,
  407. voiceSource: null,
  408. avgRingTimeLen: null,
  409. avgCallTalkTimeLen: null,
  410. avgCallEndProcessTimeLen: null,
  411. callNodeNo: null,
  412. llmAccountId: null,
  413. playTimes: null,
  414. asrProvider: null,
  415. aiTransferType: null,
  416. aiTransferData: null,
  417. autoStop: null,
  418. ivrId: null,
  419. remoteBatchId: null
  420. },
  421. // 表单参数
  422. form: {},
  423. // 表单校验
  424. rules: {
  425. batchName: [
  426. { required: true, message: "任务名称不能为空", trigger: "blur" }
  427. ],
  428. taskType: [
  429. { required: true, message: "任务类型不能为空", trigger: "change" }
  430. ],
  431. threadNum: [
  432. { required: true, message: "最大并发不能为空", trigger: "blur" },
  433. { pattern: /^[1-9]\d*$/, message: "最大并发必须为正整数", trigger: "blur" }
  434. ],
  435. gatewayId: [
  436. { required: true, message: "外呼线路不能为空", trigger: "change" }
  437. ],
  438. autoStop: [
  439. { required: true, message: "自动停止不能为空", trigger: "change" }
  440. ],
  441. llmAccountId: [
  442. { required: true, message: "大模型底座不能为空", trigger: "change" }
  443. ],
  444. voiceSource: [
  445. { required: true, message: "TTS 厂商不能为空", trigger: "change" }
  446. ],
  447. voiceCode: [
  448. { required: true, message: "音色不能为空", trigger: "change" }
  449. ],
  450. asrProvider: [
  451. { required: true, message: "ASR 厂商不能为空", trigger: "change" }
  452. ],
  453. aiTransferType: [
  454. { required: true, message: "转人工方式不能为空", trigger: "change" }
  455. ],
  456. groupId: [
  457. { validator: (rule, value, callback) => {
  458. // 任务类型为 AI 外呼(1)且转人工方式为电话排队(acd)时必填
  459. if (this.form.taskType === '1' && this.form.aiTransferType === 'acd' && !value) {
  460. callback(new Error('业务组不能为空'));
  461. } else {
  462. callback();
  463. }
  464. }, trigger: "change" }
  465. ],
  466. aiTransferExtNumber: [
  467. { validator: (rule, value, callback) => {
  468. if (this.form.aiTransferType === 'extension' && !value) {
  469. callback(new Error('转接分机不能为空'));
  470. } else {
  471. callback();
  472. }
  473. }, trigger: "blur" }
  474. ],
  475. aiTransferGatewayId: [
  476. { validator: (rule, value, callback) => {
  477. if (this.form.aiTransferType === 'gateway' && !value) {
  478. callback(new Error('外呼线路不能为空'));
  479. } else {
  480. callback();
  481. }
  482. }, trigger: "change" }
  483. ],
  484. aiTransferGatewayDestNumber: [
  485. { validator: (rule, value, callback) => {
  486. if (this.form.aiTransferType === 'gateway' && !value) {
  487. callback(new Error('转接号码不能为空'));
  488. } else {
  489. callback();
  490. }
  491. }, trigger: "blur" }
  492. ],
  493. conntectRate: [
  494. { validator: (rule, value, callback) => {
  495. if (this.form.taskType === '0' && !value) {
  496. callback(new Error('预估接通率不能为空'));
  497. } else {
  498. callback();
  499. }
  500. }, trigger: "blur" }
  501. ],
  502. avgRingTimeLen: [
  503. { validator: (rule, value, callback) => {
  504. if (this.form.taskType === '0' && !value) {
  505. callback(new Error('平均振铃时长不能为空'));
  506. } else {
  507. callback();
  508. }
  509. }, trigger: "blur" }
  510. ],
  511. avgCallTalkTimeLen: [
  512. { validator: (rule, value, callback) => {
  513. if (this.form.taskType === '0' && !value) {
  514. callback(new Error('平均通话时长不能为空'));
  515. } else {
  516. callback();
  517. }
  518. }, trigger: "blur" }
  519. ],
  520. avgCallEndProcessTimeLen: [
  521. { validator: (rule, value, callback) => {
  522. if (this.form.taskType === '0' && !value) {
  523. callback(new Error('平均事后处理时长不能为空'));
  524. } else {
  525. callback();
  526. }
  527. }, trigger: "blur" }
  528. ],
  529. playTimes: [
  530. { validator: (rule, value, callback) => {
  531. if (this.form.taskType === '2' && !value) {
  532. callback(new Error('播放次数不能为空'));
  533. } else {
  534. callback();
  535. }
  536. }, trigger: "blur" }
  537. ]
  538. }
  539. };
  540. },
  541. created() {
  542. //获取所有网关
  543. listAiSipCallGateway().then(response => {
  544. this.gatewayList = response.rows;
  545. })
  546. //获取所有音色并按厂商分类
  547. listAiSipCallVoiceTtsAliyun({voiceEnabled: 1}).then(response => {
  548. const allVoices = response.rows || [];
  549. // voiceSourceList 使用所有音色
  550. this.voiceSourceList = allVoices;
  551. // 按 voiceSource 分类
  552. this.voiceCodeLists.chinatelecom_tts = allVoices.filter(item => item.voiceSource === 'chinatelecom_tts');
  553. this.voiceCodeLists.doubao_vcl_tts = allVoices.filter(item => item.voiceSource === 'doubao_vcl_tts');
  554. this.voiceCodeLists.aliyun_tts = allVoices.filter(item => item.voiceSource === 'aliyun_tts');
  555. })
  556. //获取所有大模型底座
  557. listAiSipCallLlmAgentAccount().then(response => {
  558. this.llmAgentAccountList = response.rows;
  559. })
  560. // 获取所有业务组
  561. listAiSipCallBizGroup().then(response => {
  562. this.bizGroupList = response.rows;
  563. })
  564. this.getList();
  565. },
  566. methods: {
  567. downloadCallTemplate(){
  568. downloadTemplateByType(1).then(response => {
  569. // 创建 blob 对象
  570. const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
  571. // 创建下载链接
  572. const link = document.createElement('a');
  573. link.href = window.URL.createObjectURL(blob);
  574. link.download = 'AICallList.xlsx';
  575. // 触发下载
  576. document.body.appendChild(link);
  577. link.click();
  578. // 清理
  579. document.body.removeChild(link);
  580. window.URL.revokeObjectURL(link.href);
  581. })
  582. },
  583. startTask(batchId){
  584. startTask(batchId).then(response => {
  585. if(response.code === 200){
  586. this.msgSuccess("启动外呼成功")
  587. }else{
  588. this.msgError("启动外呼失败");
  589. }
  590. this.getList();
  591. })
  592. },
  593. stopTask(batchId){
  594. stopTask(batchId).then(response => {
  595. if(response.code === 200){
  596. this.msgSuccess("停止外呼成功")
  597. }else{
  598. this.msgError("停止外呼失败");
  599. }
  600. this.getList();
  601. })
  602. },
  603. /** 查询aiSIP外呼任务列表 */
  604. getList() {
  605. this.loading = true;
  606. listAiSipCallTask(this.queryParams).then(response => {
  607. this.aiSipCallList = response.rows;
  608. this.total = response.total;
  609. this.loading = false;
  610. });
  611. },
  612. // 取消按钮
  613. cancel() {
  614. this.open = false;
  615. this.reset();
  616. },
  617. // 表单重置
  618. reset() {
  619. this.form = {
  620. batchId: null,
  621. groupId: null,
  622. createTimeStart: null,
  623. createTimeEnd: null,
  624. batchName: null,
  625. ifcall: null,
  626. rate: null,
  627. threadNum: null,
  628. createtime: null,
  629. executing: null,
  630. stopTime: null,
  631. userid: null,
  632. taskType: null,
  633. gatewayId: null,
  634. voiceCode: null,
  635. voiceSource: null,
  636. avgRingTimeLen: null,
  637. avgCallTalkTimeLen: null,
  638. avgCallEndProcessTimeLen: null,
  639. callNodeNo: null,
  640. llmAccountId: null,
  641. playTimes: null,
  642. asrProvider: null,
  643. aiTransferType: null,
  644. aiTransferData: null,
  645. autoStop: null,
  646. ivrId: null,
  647. remoteBatchId: null,
  648. aiTransferExtNumber: null
  649. };
  650. this.resetForm("form");
  651. },
  652. /** 搜索按钮操作 */
  653. handleQuery() {
  654. this.queryParams.pageNum = 1;
  655. this.getList();
  656. },
  657. /** 重置按钮操作 */
  658. resetQuery() {
  659. this.resetForm("queryForm");
  660. this.handleQuery();
  661. },
  662. // 多选框选中数据
  663. handleSelectionChange(selection) {
  664. this.ids = selection.map(item => item.batchId)
  665. this.single = selection.length!==1
  666. this.multiple = !selection.length
  667. },
  668. /** 新增按钮操作 */
  669. handleAdd() {
  670. this.reset();
  671. this.open = true;
  672. this.title = "添加 aiSIP 外呼任务";
  673. // 默认选中 AI 外呼
  674. this.form.taskType = "1";
  675. // 默认自动停止
  676. this.form.autoStop = "1";
  677. // 默认选中第一个外呼线路
  678. if (this.gatewayList && this.gatewayList.length > 0) {
  679. this.form.gatewayId = this.gatewayList[0].id;
  680. }
  681. // 默认选中第一个大模型底座
  682. if (this.llmAgentAccountList && this.llmAgentAccountList.length > 0) {
  683. this.form.llmAccountId = this.llmAgentAccountList[0].id;
  684. }
  685. // 默认选中第一个 TTS 厂商
  686. this.form.voiceSource = "chinatelecom_tts";
  687. // 根据选中的 TTS 厂商设置默认音色
  688. this.$nextTick(() => {
  689. if (this.voiceCodeLists.chinatelecom_tts && this.voiceCodeLists.chinatelecom_tts.length > 0) {
  690. this.form.voiceCode = this.voiceCodeLists.chinatelecom_tts[0].voiceCode;
  691. }
  692. });
  693. // 默认选中第一个 ASR 厂商
  694. this.form.asrProvider = "chinatelecom";
  695. // 默认选中第一个转人工方式
  696. this.form.aiTransferType = "acd";
  697. // 根据转人工方式设置默认业务组
  698. this.$nextTick(() => {
  699. if (this.bizGroupList && this.bizGroupList.length > 0) {
  700. this.form.groupId = this.bizGroupList[0].groupId;
  701. }
  702. });
  703. },
  704. /** 修改按钮操作 */
  705. handleUpdate(row) {
  706. this.reset();
  707. const batchId = row.batchId || this.ids
  708. getAiSipCallTask(batchId).then(response => {
  709. this.form = response.data;
  710. // 如果有 voiceSource,设置对应的音色列表
  711. if (this.form.voiceSource) {
  712. // 不需要重新请求,直接使用已加载的数据
  713. }
  714. this.open = true;
  715. this.title = "修改 aiSIP 外呼任务";
  716. // 如果是纯人工预测外呼,设置默认的并发相关字段
  717. if (this.form.taskType === '0') {
  718. if (!this.form.conntectRate) {
  719. this.form.conntectRate = '80';
  720. }
  721. if (!this.form.avgRingTimeLen) {
  722. this.form.avgRingTimeLen = '30';
  723. }
  724. if (!this.form.avgCallTalkTimeLen) {
  725. this.form.avgCallTalkTimeLen = '60';
  726. }
  727. if (!this.form.avgCallEndProcessTimeLen) {
  728. this.form.avgCallEndProcessTimeLen = '30';
  729. }
  730. }
  731. // 如果是语音通知,设置默认播放次数
  732. if (this.form.taskType === '2') {
  733. if (!this.form.playTimes) {
  734. this.form.playTimes = '1';
  735. }
  736. }
  737. });
  738. },
  739. /** 提交按钮 */
  740. submitForm() {
  741. this.$refs["form"].validate(valid => {
  742. if (valid) {
  743. if (this.form.batchId != null) {
  744. updateAiSipCallTask(this.form).then(() => {
  745. this.msgSuccess("修改成功");
  746. this.open = false;
  747. this.getList();
  748. });
  749. } else {
  750. addAiSipCallTask(this.form).then(() => {
  751. this.msgSuccess("新增成功");
  752. this.open = false;
  753. this.getList();
  754. });
  755. }
  756. }
  757. });
  758. },
  759. /** 删除按钮操作 */
  760. handleDelete(row) {
  761. const batchIds = row.batchId || this.ids;
  762. this.$confirm('是否确认删除aiSIP外呼任务编号为"' + batchIds + '"的数据项?', "警告", {
  763. confirmButtonText: "确定",
  764. cancelButtonText: "取消",
  765. type: "warning"
  766. }).then(function() {
  767. return delAiSipCallTask(batchIds);
  768. }).then(() => {
  769. this.getList();
  770. this.msgSuccess("删除成功");
  771. }).catch(() => {});
  772. },
  773. /** 导出按钮操作 */
  774. handleExport() {
  775. const queryParams = this.queryParams;
  776. this.$confirm('是否确认导出所有 aiSIP 外呼任务数据项?', "警告", {
  777. confirmButtonText: "确定",
  778. cancelButtonText: "取消",
  779. type: "warning"
  780. }).then(() => {
  781. this.exportLoading = true;
  782. return exportAiSipCallTask(queryParams);
  783. }).then(response => {
  784. this.download(response.msg);
  785. this.exportLoading = false;
  786. }).catch(() => {});
  787. },
  788. /** 导入数据按钮操作 */
  789. handleImportData(batchId) {
  790. // 创建隐藏的文件输入框
  791. const input = document.createElement('input');
  792. input.type = 'file';
  793. input.accept = '.xlsx,.xls,.csv';
  794. input.style.display = 'none';
  795. input.onchange = (event) => {
  796. const files = event.target.files;
  797. if (!files || files.length === 0) {
  798. return;
  799. }
  800. const file = files[0];
  801. // 验证文件类型
  802. const isExcelOrCSV = file.type === 'application/vnd.ms-excel' ||
  803. file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
  804. file.type === 'text/csv' ||
  805. file.name.endsWith('.xlsx') ||
  806. file.name.endsWith('.xls') ||
  807. file.name.endsWith('.csv');
  808. const isLt100M = file.size / 1024 / 1024 < 100;
  809. if (!isExcelOrCSV) {
  810. this.msgError('只能上传 xlsx/xls/csv 文件!');
  811. return;
  812. }
  813. if (!isLt100M) {
  814. this.msgError('上传文件大小不能超过 100MB!');
  815. return;
  816. }
  817. // 创建 FormData 对象并调用接口
  818. const formData = new FormData();
  819. formData.append('batchId', batchId);
  820. formData.append('file', file);
  821. commonImportExcel(formData).then(response => {
  822. if (response.code === 200) {
  823. this.msgSuccess('导入成功');
  824. this.getList();
  825. } else {
  826. this.msgError(response.msg || '导入失败');
  827. }
  828. }).catch(error => {
  829. this.msgError('导入失败:' + error.message);
  830. });
  831. };
  832. document.body.appendChild(input);
  833. input.click();
  834. document.body.removeChild(input);
  835. },
  836. // 获取当前选中厂商的音色列表
  837. getCurrentVoiceCodeList() {
  838. if (!this.form.voiceSource) {
  839. return [];
  840. }
  841. return this.voiceCodeLists[this.form.voiceSource] || [];
  842. },
  843. // 处理 TTS 厂商变化
  844. handleVoiceSourceChange() {
  845. // 清空已选音色
  846. this.form.voiceCode = null;
  847. // 默认选中当前厂商的第一个音色
  848. this.$nextTick(() => {
  849. const currentList = this.voiceCodeLists[this.form.voiceSource];
  850. if (currentList && currentList.length > 0) {
  851. this.form.voiceCode = currentList[0].voiceCode;
  852. }
  853. });
  854. },
  855. // 处理转人工方式变化
  856. handleAiTransferTypeChange() {
  857. if (this.form.aiTransferType === 'extension') {
  858. // 选中转分机时,清空业务组
  859. this.form.groupId = null;
  860. } else if (this.form.aiTransferType === 'acd' || this.form.aiTransferType === 'gateway') {
  861. // 选中电话排队或转外部网关时,清空转接分机
  862. this.form.aiTransferExtNumber = null;
  863. }
  864. },
  865. // 处理文件变化
  866. handleFileChange(event) {
  867. const files = event.target.files;
  868. if (!files || files.length === 0) {
  869. return;
  870. }
  871. const file = files[0];
  872. // 验证文件类型
  873. const isExcelOrCSV = file.type === 'application/vnd.ms-excel' ||
  874. file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
  875. file.type === 'text/csv' ||
  876. file.name.endsWith('.xlsx') ||
  877. file.name.endsWith('.xls') ||
  878. file.name.endsWith('.csv');
  879. const isLt100M = file.size / 1024 / 1024 < 100;
  880. if (!isExcelOrCSV) {
  881. this.msgError('只能上传 xlsx/xls/csv 文件!');
  882. return;
  883. }
  884. if (!isLt100M) {
  885. this.msgError('上传文件大小不能超过 100MB!');
  886. return;
  887. }
  888. // 创建 FormData 对象并调用接口
  889. const formData = new FormData();
  890. formData.append('batchId', this.uploadForm.batchId);
  891. formData.append('file', file);
  892. // 调用 importExcel 接口
  893. commonImportExcel(formData).then(response => {
  894. if (response.code === 200) {
  895. this.msgSuccess('导入成功');
  896. this.uploadOpen = false;
  897. this.getList();
  898. } else {
  899. this.msgError(response.msg || '导入失败');
  900. }
  901. }).catch(error => {
  902. this.msgError('导入失败:' + error.message);
  903. });
  904. },
  905. }
  906. };
  907. </script>