<template> <div class="app-container"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="110px"> <el-form-item label="被叫手机号" prop="mobile"> <el-input v-model="queryParams.mobile" placeholder="请输入被叫手机号" clearable @keyup.enter="handleQuery" /> </el-form-item> <el-form-item label="模板" prop="callerNum"> <el-select v-model="queryParams.robotId" placeholder="请选择模板" :clearable="true" > <el-option v-for="item in callTemplate" :key="item.robotId" :label="item.robotName" :value="item.robotId" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['outcall:unitVoiceData:remove']" >删除</el-button > </el-form-item> </el-form> <el-table v-loading="loading" :data="unitVoiceDataList" @selection-change="handleSelectionChange"> <el-table-column type="index" width="55" align="center" prop="序号" /> <el-table-column type="selection" width="55" align="center" /> <el-table-column label="被叫手机号" align="center" prop="mobile" /> <el-table-column label="模板" align="center" prop="robotId"> <template #default="scope"> <span v-if="scope.row.robotId">{{ templateCallText(scope.row.robotId) }}</span> </template> </el-table-column> <el-table-column label="呼出手机号" align="center" prop="callerNum" /> <el-table-column label="呼叫时间" align="center" prop="startTime" width="180"> <!-- <template #default="scope"> <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span> </template> --> </el-table-column> <el-table-column label="响铃开始时间" align="center" prop="ringTartTime" width="180"> <!-- <template #default="scope"> <span>{{ parseTime(scope.row.ringTartTime, '{y}-{m}-{d}') }}</span> </template> --> </el-table-column> <el-table-column label="通话开始时间" align="center" prop="talkingStartTime" width="180"> <!-- <template #default="scope"> <span>{{ parseTime(scope.row.talkingStartTime, '{y}-{m}-{d}') }}</span> </template> --> </el-table-column> <el-table-column label="呼叫结束时间" align="center" prop="endTime" width="180"> <!-- <template #default="scope"> <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span> </template> --> </el-table-column> <el-table-column label="响铃时长(秒)" align="center" prop="ringingTimeLen" /> <el-table-column label="通话时长(秒)" align="center" prop="talkingTimeLen" /> <!-- <el-table-column label="回调数据类型, 0-任务呼叫单通电话回调 1-号码组终态回调 2-任务状态变更回调 3-实时呼叫单通电话回调" align="center" prop="callbackType" /> --> <!-- <el-table-column label="未接通原因,详情见枚举类" align="center" prop="endTypeReason" /> --> <el-table-column label="呼叫状态" align="center" prop="isRobotHangup" width="180"> <template #default="scope"> <div class="connectStatus" v-if="scope.row.endTypeReason=='success'"> <span class="circleColor greenColor"></span> <span >已接通</span> </div> <div v-else class="connectStatus" > <span class="circleColor redColor"></span> <span>未接通</span> <span v-if="scope.row.endTypeReasonStr">{{'('+scope.row.endTypeReasonStr+')'}}</span> </div> </template> </el-table-column> <!-- <el-table-column label="通话录音文件" align="center" prop="recordVoice" /> --> <!-- <el-table-column label="备注" align="center" prop="remark" /> <el-table-column label="状态" align="center" prop="status" /> --> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="230px"> <template #default="scope"> <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['outcall:unitVoiceData:remove']" >删除</el-button > <!-- <el-button link type="primary" icon="View" @click="handleDetail(scope.row)" v-hasPermi="['outcall:unitVoiceData:view']">详情</el-button> --> <!-- <el-button link type="primary" icon="Notebook" @click="callRecords(scope.row)">通话记录</el-button> --> <el-button link type="primary" icon="Notebook" @click="callRecords2(scope.row)">通话记录</el-button> </template> </el-table-column> </el-table> <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> <!-- 添加或修改语音外呼-呼叫记录对话框 --> <el-dialog :title="title" v-model="open" width="500px" append-to-body> <el-form ref="unitVoiceDataRef" :model="form" :rules="rules" label-width="100px"> <el-form-item label="被叫手机号" prop="mobile"> <el-input v-model="form.mobile" placeholder="请输入被叫手机号" /> </el-form-item> <el-form-item label="呼出手机号" prop="callerNum"> <el-input v-model="form.callerNum" placeholder="请输入呼出手机号" /> </el-form-item> <el-form-item label="呼叫时间" prop="startTime"> <el-date-picker clearable v-model="form.startTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择呼叫时间"> </el-date-picker> </el-form-item> <el-form-item label="响铃开始时间" prop="ringTartTime"> <el-date-picker clearable v-model="form.ringTartTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择响铃开始时间" > </el-date-picker> </el-form-item> <el-form-item label="通话开始时间" prop="talkingStartTime"> <el-date-picker clearable v-model="form.talkingStartTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择通话开始时间" > </el-date-picker> </el-form-item> <el-form-item label="呼叫结束时间" prop="endTime"> <el-date-picker clearable v-model="form.endTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择呼叫结束时间"> </el-date-picker> </el-form-item> <el-form-item label="响铃时长(秒)" prop="ringingTimeLen"> <el-input v-model="form.ringingTimeLen" placeholder="请输入响铃时长(秒)" /> </el-form-item> <el-form-item label="通话时长(秒)" prop="talkingTimeLen"> <el-input v-model="form.talkingTimeLen" placeholder="请输入通话时长(秒)" /> </el-form-item> <!-- <el-form-item label="未接通原因,详情见枚举类" prop="endTypeReason"> <el-input v-model="form.endTypeReason" placeholder="请输入未接通原因,详情见枚举类" /> </el-form-item> --> <el-form-item label="是否是机器人挂断 true 和 false" prop="isRobotHangup"> <el-input v-model="form.isRobotHangup" placeholder="请输入是否是机器人挂断 true 和 false" /> </el-form-item> <el-form-item label="通话录音文件" prop="recordVoice"> <el-input v-model="form.recordVoice" placeholder="请输入通话录音文件" /> </el-form-item> <el-form-item label="备注" prop="remark"> <el-input v-model="form.remark" placeholder="请输入备注" /> </el-form-item> <el-form-item label="删除标志" prop="delFlag"> <el-input v-model="form.delFlag" placeholder="请输入删除标志" /> </el-form-item> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitForm">确 定</el-button> <el-button @click="cancel">取 消</el-button> </div> </template> </el-dialog> <!-- 语音外呼-呼叫记录详情 --> <el-dialog title="语音外呼-呼叫记录详情" v-model="detailOpen" width="800px" append-to-body class="dialog-detail-box"> <div class="dialog-form-detail flex flex-r flex-wrap"> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">被叫手机号</div> <div class="detail-value flex flex-align-center"> {{ dialogFormDetail.mobile }} </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">呼出手机号</div> <div class="detail-value flex flex-align-center"> {{ dialogFormDetail.callerNum }} </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">呼叫时间</div> <div class="detail-value flex flex-align-center"> <span>{{ parseTime(dialogFormDetail.startTime, '{y}-{m}-{d}') }}</span> </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">响铃开始时间</div> <div class="detail-value flex flex-align-center"> <span>{{ parseTime(dialogFormDetail.ringTartTime, '{y}-{m}-{d}') }}</span> </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">通话开始时间</div> <div class="detail-value flex flex-align-center"> <span>{{ parseTime(dialogFormDetail.talkingStartTime, '{y}-{m}-{d}') }}</span> </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">呼叫结束时间</div> <div class="detail-value flex flex-align-center"> <span>{{ parseTime(dialogFormDetail.endTime, '{y}-{m}-{d}') }}</span> </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">响铃时长(秒)</div> <div class="detail-value flex flex-align-center"> {{ dialogFormDetail.ringingTimeLen }} </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">通话时长(秒)</div> <div class="detail-value flex flex-align-center"> {{ dialogFormDetail.talkingTimeLen }} </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center"> 回调数据类型, 0-任务呼叫单通电话回调 1-号码组终态回调 2-任务状态变更回调 3-实时呼叫单通电话回调 </div> <div class="detail-value flex flex-align-center"> {{ dialogFormDetail.callbackType }} </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">未接通原因,详情见枚举类</div> <div class="detail-value flex flex-align-center"> {{ dialogFormDetail.endTypeReason }} </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">是否是机器人挂断 true 和 false</div> <div class="detail-value flex flex-align-center"> {{ dialogFormDetail.isRobotHangup }} </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">通话录音文件</div> <div class="detail-value flex flex-align-center"> {{ dialogFormDetail.recordVoice }} </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">备注</div> <div class="detail-value flex flex-align-center"> {{ dialogFormDetail.remark }} </div> </div> <div class="flex flex-r"> <div class="detail-label flex flex-align-center">状态</div> <div class="detail-value flex flex-align-center"> {{ dialogFormDetail.status }} </div> </div> </div> <template #footer> <div class="dialog-footer"> <el-button @click="cancel">关 闭</el-button> </div> </template> </el-dialog> <!-- 通话记录 --> <el-dialog title="通话记录" v-model="callRecordsModel" width="1100px" append-to-body> <div style="padding-bottom: 30px"> <el-table v-loading="callRecordsloading" :data="callRecordsDataList"> <el-table-column type="index" width="55" align="center" prop="序号" /> <el-table-column label="语音内容" align="center" width="500" prop="contextText" /> <el-table-column label="开始" align="center" prop="start" width="180"> <template #default="scope"> <span>{{ parseTime(scope.row.start, '{y}-{m}-{d}') }}</span> </template> </el-table-column> <el-table-column label="结束" align="center" prop="stop" width="180"> <template #default="scope"> <span>{{ parseTime(scope.row.stop, '{y}-{m}-{d}') }}</span> </template> </el-table-column> <el-table-column label="时长" align="center" prop="timeLen" /> <!-- <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="230px"> <template #default="scope"> <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['outcall:unitVoiceData:remove']">删除</el-button> </template> </el-table-column> --> </el-table> <pagination v-show="callRecordstotal > 0" :total="callRecordstotal" v-model:page="callRecordspageNum" v-model:limit="callRecordspageSize" @pagination="RecordsData" /> </div> </el-dialog> <el-drawer v-model="drawer" class="callDrawer" :open-delay="100" size="1100px" title="通话记录" :with-header="false" @close="drawerClose"> <div class="flex"> <div class="leftBoxText"> <div class="phone">{{atcallRecords.mobile}}</div> <!-- <div class="datalist"> <el-select v-model="datalistValue" placeholder="Select" style="width: 100px"> <el-option v-for="item in datalist" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </div> --> <div class="audioBox" > <audio style="width:100%" ref="audioPlayer" controls :src="atcallRecords.recordVoice"></audio> </div> <!-- 文本转换详情 --> <div class="textInfo"> <div> <div class="callDuration" v-if="atcallRecords.talkingStartTime">{{atcallRecords.talkingStartTime}}</div> <!-- 通话 --> <div v-for="(item) in callRecordsDataList" :key="item.id"> <!-- 左 --> <div class="NewsBox leftNews" v-if="item.role=='speech'"> <img class="Robotimg" src="@/assets/images/callRobot.png" alt="" /> <div class=""> <div> <span class="timeSum">{{isTotalFun(item.timeLen)}}″</span> <span>{{cutOutFun(item.start)+'-'+cutOutFun(item.stop)}}</span> </div> <div class="textBgc"> <span> <img class="voiceimg" src="@/assets/images/voice.png" alt="" /> </span> <span> {{item.contextText}} </span> </div> </div> </div> <!-- 右 --> <div class="NewsBox rightNews" v-if="item.role=='voice'"> <img class="Robotimg" src="@/assets/images/callUser.png" alt="" /> <div> <div> <span class="timeSum">{{isTotalFun(item.timeLen)}}″</span> <span>{{cutOutFun(item.start)+'-'+cutOutFun(item.stop)}}</span> </div> <div class="textBgc"> <span> <img class="voiceimg" src="@/assets/images/voice.png" alt="" /> </span> <span> {{item.contextText}} </span> </div> </div> </div> </div> </div> </div> </div> <div class="rightBoxInfo"> <el-tabs v-model="activeName" class="demo-tabs"> <el-tab-pane label="通话信息" name="first"> <div class="infoBox"> <div class="eachInfo"> <span class="labelText">被叫号码:</span> <span class="valueText">{{atcallRecords.mobile}}</span> </div> <div class="eachInfo"> <span class="labelText">主叫号码:</span> <span class="valueText">{{atcallRecords.callerNum}}</span> </div> <div class="eachInfo"> <span class="labelText">呼出状态:</span> <!-- <span class="valueText">{{atcallRecords.endTypeReason}}</span> --> <div class="valueText connectStatus" v-if="atcallRecords.endTypeReason=='success'"> <span class="circleColor greenColor"></span> <span >已接通</span> </div> <div v-else class="valueText connectStatus" > <span class="circleColor redColor"></span> <span>未接通</span> <span>{{'('+atcallRecords.endTypeReasonStr+')'}}</span> </div> </div> <div class="eachInfo"> <span class="labelText">振铃时长(秒):</span> <span class="valueText">{{atcallRecords.ringingTimeLen}}</span> </div> <div class="eachInfo"> <span class="labelText">通话时长(秒):</span> <span class="valueText">{{atcallRecords.talkingTimeLen}}</span> </div> <div class="eachInfo"> <span class="labelText">呼叫时间:</span> <span class="valueText">{{atcallRecords.startTime}}</span> </div> <div class="eachInfo"> <span class="labelText">振铃开始时间:</span> <span class="valueText">{{atcallRecords.ringTartTime}}</span> </div> <div class="eachInfo"> <span class="labelText">通话开始时间:</span> <span class="valueText">{{atcallRecords.startTime}}</span> </div> <div class="eachInfo"> <span class="labelText">呼叫结束时间:</span> <span class="valueText">{{atcallRecords.endTime}}</span> </div> <div class="eachInfo"> <span class="labelText">挂断方:</span> <span class="valueText">{{atcallRecords.isRobotHangup==1?'机器人挂断':'用户挂断'}}</span> </div> <!-- <div class="eachInfo"> <div class="labelText">通话ID:</div> <div class="valueText">1234567iffgh543456787654345678765432654</div> </div> <div class="eachInfo"> <div class="labelText">通话ID:</div> <div class="valueText">1234567iffgh543456787654345678765432654</div> </div> <div class="eachInfo"> <div class="labelText">呼出手机号:</div> <div class="valueText">13454545455</div> </div> <div class="eachInfo"> <div class="labelText">通话ID</div> <div class="valueText">1234567iffgh543456787654345678765432654</div> </div> <div class="eachInfo"> <div class="labelText">通话ID</div> <div class="valueText">1234567iffgh543456787654345678765432654</div> </div> --> </div> </el-tab-pane> </el-tabs> </div> </div> </el-drawer> </div> </template> <script setup name="UnitVoiceData"> import { pageunitVoiceData, delunitVoiceData, updateunitVoiceData, pageunitVoiceRecord, unitVoiceRecordList } from '@/api/OutgoingCall/notificationRecord'; import { listunitVoiceTemplate } from '@/api/OutgoingCall/templateList'; const { proxy } = getCurrentInstance(); const unitVoiceDataList = ref([]); const open = ref(false); const loading = ref(true); const showSearch = ref(true); const ids = ref([]); const single = ref(true); const multiple = ref(true); const total = ref(0); const title = ref(''); const detailOpen = ref(false); const data = reactive({ form: {}, queryParams: { pageNum: 1, pageSize: 10, mobile: null, callerNum: null, robotId:null }, rules: { mobile: [{ required: true, message: '被叫手机号不能为空', trigger: 'blur' }], callerNum: [{ required: true, message: '呼出手机号不能为空', trigger: 'blur' }], ringingTimeLen: [{ required: true, message: '响铃时长(秒)不能为空', trigger: 'blur' }], talkingTimeLen: [{ required: true, message: '通话时长(秒)不能为空', trigger: 'blur' }], callbackType: [ { required: true, message: '回调数据类型, 0-任务呼叫单通电话回调 1-号码组终态回调 2-任务状态变更回调 3-实时呼叫单通电话回调不能为空', trigger: 'change', }, ], endTypeReason: [{ required: true, message: '未接通原因,详情见枚举类不能为空', trigger: 'blur' }], isRobotHangup: [{ required: true, message: '是否是机器人挂断 true 和 false不能为空', trigger: 'blur' }], recordVoice: [{ required: true, message: '通话录音文件不能为空', trigger: 'blur' }], }, dialogFormDetail: {}, //详情弹框数据 }); const { queryParams, form, rules, dialogFormDetail } = toRefs(data); // 获取模板 const callTemplate = ref(false); function getTemplateData() { listunitVoiceTemplate().then(response => { callTemplate.value = response.data; }); } function templateCallText(id){ console.log('模板id',id); console.log('callTemplate',callTemplate); let str='' callTemplate.value.forEach(e => { if(e.robotId==id){ str= e.robotName } }); return str } /** 查询语音外呼-呼叫记录列表 */ function getList() { loading.value = true; pageunitVoiceData(queryParams.value).then(response => { unitVoiceDataList.value = response.data; total.value = response.total; loading.value = false; }); } // 取消按钮 function cancel() { open.value = false; detailOpen.value = false; reset(); } // 表单重置 function reset() { form.value = { id: null, mobile: null, callerNum: null, robotId: null, }; proxy.resetForm('unitVoiceDataRef'); } /** 搜索按钮操作 */ function handleQuery() { queryParams.value.pageNum = 1; getList(); } /** 重置按钮操作 */ function resetQuery() { proxy.resetForm('queryRef'); handleQuery(); } // 多选框选中数据 function handleSelectionChange(selection) { ids.value = selection.map(item => item.id); single.value = selection.length != 1; multiple.value = !selection.length; } /** 删除按钮操作 */ function handleDelete(row) { const _ids = row.id || ids.value; proxy.$modal .confirm('是否确认删除语音外呼-呼叫记录编号为"' + _ids + '"的数据项?') .then(function () { return delunitVoiceData(_ids); }) .then(() => { getList(); proxy.$modal.msgSuccess('删除成功'); }) .catch(() => {}); } //查看详情操作 function handleDetail(row) { detailOpen.value = true; dialogFormDetail.value = row; } const callRecordsModel = ref(false); const callRecordsloading = ref(false); const callRecordsDataList = ref([]); // 通话记录 const atcallRecords = ref([]); function callRecords(row) { atcallRecords.value = row; RecordsData(); } const callRecordspageNum = ref(1); const callRecordspageSize = ref(10); const callRecordstotal = ref(0); function RecordsData2() { callRecordsModel.value = true; callRecordsloading.value = true; let params = { dataId: atcallRecords.value.id, pageNum: callRecordspageNum.value, pageSize: callRecordspageSize.value, }; pageunitVoiceRecord(params).then(response => { callRecordsDataList.value = response.data; callRecordstotal.value = response.total; callRecordsloading.value = false; }); } // 右测弹框通话记录 const datalist = ref([ { value: 'Option1', label: 'Option1', }, ]); const drawer = ref(false); const datalistValue = ref(''); function callRecords2(row) { atcallRecords.value = row; RecordsData(); } function RecordsData() { drawer.value = true; callRecordsloading.value = true; let params = { dataId: atcallRecords.value.id, }; unitVoiceRecordList(params).then(response => { callRecordsDataList.value = response.data; callRecordsloading.value = false; }); } // 通话信息 const activeName = ref('first'); // 保留2位小数 function isTotalFun(sum) { let z=(Number(sum/1000)).toFixed(2) return z; } // 截取 function cutOutFun(str) { let index = str.indexOf("."); let result = str.substring(0, index); return result } function drawerClose() { atcallRecords.value={} callRecordsDataList.value={} } getTemplateData() getList(); </script> <style lang="scss" scoped> .connectStatus { .circleColor{ display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 3px; } .greenColor { background: #67C23A; } .redColor { background: #F56C6C; } } .callDrawer { .leftBoxText { background: #eef4ff; padding: 20px; height: 100vh; flex: 1; box-sizing: border-box; .phone { font-weight: 700; font-size: 22px; margin-bottom: 10px; } .audioBox { background: rgba(255, 255, 255, 0.6); border-radius: 10px; margin: 10px 0; audio::-webkit-media-controls-enclosure { background-color: unset; } } .textInfo { color: #8894b2; padding-top: 20px; font-size: 14px; height: calc(100vh - 170px); overflow-y: auto; .callDuration { text-align: center; margin-bottom: 20px; } .NewsBox { display: flex; align-items: center; margin-bottom: 50px; .Robotimg { width: 38px; height: 38px; } .voiceimg { width: 24px; height: 24px; } .textBgc { background: #ffffff; padding: 8px; display: flex; align-items: center; margin-top: 10px; span { color: #333; } } .timeSum { // display: inline-block; margin-right: 10px; color: #333; } } .leftNews { .Robotimg { margin-right: 10px; } .textBgc { border-radius: 20px 20px 20px 0; } } .rightNews { text-align: right; flex-direction: row-reverse; .textBgc { border-radius: 20px 20px 0 20px; flex-direction: row-reverse } .voiceimg { transform: rotate(180deg); } .Robotimg { margin-left: 10px; } .textBgc { span { text-align: left; } } } } } // 通话信息 .rightBoxInfo { width: 400px; padding-top: 20px; box-sizing: border-box; :deep(.el-tabs__item) { padding-left: 20px!important; } .infoBox { padding: 20px; .eachInfo { display: flex; align-items: center; font-size: 14px; color: #8894b2; margin-bottom: 20px; .labelText { min-width: 100px; } .valueText{ word-break: break-all; } } } } } </style>>