From 607ff6aa41463546456e18b9c14274bad281d098 Mon Sep 17 00:00:00 2001 From: 18226920803 Date: Wed, 10 Jun 2026 09:44:35 +0800 Subject: [PATCH] =?UTF-8?q?=E7=83=AD=E7=BA=BF=E5=AE=A2=E6=9C=8D=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E5=8F=B0=E5=8A=9F=E8=83=BD=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/sql/业务脚本.sql | 55 +++ smart-flow-api/pom.xml | 8 +- .../phone/controller/PhoneController.java | 41 ++ .../business/phone/dao/PhoneRecordDao.java | 17 + .../domain/entity/PhoneRecordEntity.java | 76 +++ .../domain/form/PhoneRecordQueryForm.java | 48 ++ .../phone/domain/vo/PhoneRecordVO.java | 74 +++ .../business/phone/service/PhoneService.java | 141 ++++++ smart-flow-api/sa-base/pom.xml | 8 +- .../src/api/business/phone/phone-api.js | 11 + smart-flow-web/src/utils/phone-websocket.js | 151 ++++++ .../src/views/jwpy/myrx/desk/index.vue | 463 +++++++++++++++++- 12 files changed, 1076 insertions(+), 17 deletions(-) create mode 100644 docs/sql/业务脚本.sql create mode 100644 smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/controller/PhoneController.java create mode 100644 smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/dao/PhoneRecordDao.java create mode 100644 smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/domain/entity/PhoneRecordEntity.java create mode 100644 smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/domain/form/PhoneRecordQueryForm.java create mode 100644 smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/domain/vo/PhoneRecordVO.java create mode 100644 smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/service/PhoneService.java create mode 100644 smart-flow-web/src/api/business/phone/phone-api.js create mode 100644 smart-flow-web/src/utils/phone-websocket.js diff --git a/docs/sql/业务脚本.sql b/docs/sql/业务脚本.sql new file mode 100644 index 0000000..dba83a7 --- /dev/null +++ b/docs/sql/业务脚本.sql @@ -0,0 +1,55 @@ +-- ============================================= +-- 电话管理系统 - 人大金仓数据库表结构 +-- 创建日期: 2026-06-09 +-- ============================================= + +-- 1. 电话记录表 +CREATE TABLE IF NOT EXISTS t_phone_record ( + id BIGINT PRIMARY KEY, + call_id VARCHAR(100) NOT NULL, + caller_number VARCHAR(50), + caller_name VARCHAR(100), + callee_number VARCHAR(50), + callee_name VARCHAR(100), + call_direction VARCHAR(20), + call_start_time TIMESTAMP, + call_end_time TIMESTAMP, + call_duration INTEGER, + call_status VARCHAR(20), + recording_url VARCHAR(500), + has_recording INTEGER DEFAULT 0, + call_type VARCHAR(20), + remark TEXT, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted INTEGER DEFAULT 0 +); + +COMMENT ON TABLE t_phone_record IS '电话记录表'; +COMMENT ON COLUMN t_phone_record.id IS '主键ID'; +COMMENT ON COLUMN t_phone_record.call_id IS '通话ID(呼叫系统唯一标识)'; +COMMENT ON COLUMN t_phone_record.caller_number IS '主叫号码'; +COMMENT ON COLUMN t_phone_record.caller_name IS '主叫姓名'; +COMMENT ON COLUMN t_phone_record.callee_number IS '被叫号码'; +COMMENT ON COLUMN t_phone_record.callee_name IS '被叫姓名'; +COMMENT ON COLUMN t_phone_record.call_direction IS '通话方向:IN-呼入, OUT-呼出'; +COMMENT ON COLUMN t_phone_record.call_start_time IS '通话开始时间'; +COMMENT ON COLUMN t_phone_record.call_end_time IS '通话结束时间'; +COMMENT ON COLUMN t_phone_record.call_duration IS '通话时长(秒)'; +COMMENT ON COLUMN t_phone_record.call_status IS '通话状态:ANSWERED-已接通, MISSED-未接听, BUSY-忙线, REJECTED-拒接'; +COMMENT ON COLUMN t_phone_record.recording_url IS '录音文件URL'; +COMMENT ON COLUMN t_phone_record.has_recording IS '是否有录音:0-无, 1-有'; +COMMENT ON COLUMN t_phone_record.call_type IS '通话类型:NORMAL-普通, TRANSFER-转接, CONFERENCE-会议'; +COMMENT ON COLUMN t_phone_record.remark IS '备注'; +COMMENT ON COLUMN t_phone_record.create_time IS '创建时间'; +COMMENT ON COLUMN t_phone_record.update_time IS '更新时间'; +COMMENT ON COLUMN t_phone_record.deleted IS '逻辑删除:0-未删除, 1-已删除'; + +-- 创建索引 +CREATE INDEX idx_phone_record_call_id ON t_phone_record(call_id); +CREATE INDEX idx_phone_record_caller_number ON t_phone_record(caller_number); +CREATE INDEX idx_phone_record_call_start_time ON t_phone_record(call_start_time); +CREATE INDEX idx_phone_record_call_status ON t_phone_record(call_status); + +-- 序列(用于主键自增) +CREATE SEQUENCE IF NOT EXISTS seq_phone_record_id START WITH 1 INCREMENT BY 1; \ No newline at end of file diff --git a/smart-flow-api/pom.xml b/smart-flow-api/pom.xml index a8e75f4..664e5d9 100644 --- a/smart-flow-api/pom.xml +++ b/smart-flow-api/pom.xml @@ -123,6 +123,12 @@ ${p6spy.version} + + org.springframework.boot + spring-boot-starter-websocket + ${springboot.version} + + cn.com.kingbase kingbase8 @@ -472,4 +478,4 @@ - + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/controller/PhoneController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/controller/PhoneController.java new file mode 100644 index 0000000..6d60218 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/controller/PhoneController.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.admin.module.business.phone.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.module.business.phone.domain.form.PhoneRecordQueryForm; +import net.lab1024.sa.admin.module.business.phone.domain.vo.PhoneRecordVO; +import net.lab1024.sa.admin.module.business.phone.service.PhoneService; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.springframework.web.bind.annotation.*; + +/** + * 电话管理控制器 + * + * @Author SmartFlow + * @Date 2026-06-09 + */ +@RestController +@Tag(name = "业务功能-电话管理") +public class PhoneController { + + @Resource + private PhoneService phoneService; + + @Operation(summary = "分页查询电话记录") + @PostMapping("/phone/record/queryPage") + public ResponseDTO> queryPhoneRecordPage(@RequestBody @Valid PhoneRecordQueryForm queryForm) { + return phoneService.queryPhoneRecordPage(queryForm); + } + + @Operation(summary = "模拟来电(测试用)") + @PostMapping("/phone/simulate/incomingCall") + public ResponseDTO simulateIncomingCall( + @RequestParam String callerNumber, + @RequestParam String callerName, + @RequestParam String agentId) { + return phoneService.simulateIncomingCall(callerNumber, callerName, agentId); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/dao/PhoneRecordDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/dao/PhoneRecordDao.java new file mode 100644 index 0000000..126d0dc --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/dao/PhoneRecordDao.java @@ -0,0 +1,17 @@ +package net.lab1024.sa.admin.module.business.phone.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.admin.module.business.phone.domain.entity.PhoneRecordEntity; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +/** + * 电话记录DAO + * + * @Author SmartFlow + * @Date 2026-06-09 + */ +@Mapper +@Component +public interface PhoneRecordDao extends BaseMapper { +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/domain/entity/PhoneRecordEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/domain/entity/PhoneRecordEntity.java new file mode 100644 index 0000000..44ce718 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/domain/entity/PhoneRecordEntity.java @@ -0,0 +1,76 @@ +package net.lab1024.sa.admin.module.business.phone.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 电话记录实体类 + * + * @Author SmartFlow + * @Date 2026-06-09 + */ +@Data +@TableName("t_phone_record") +@Schema(description = "电话记录") +public class PhoneRecordEntity { + + @TableId(type = IdType.INPUT) + @Schema(description = "主键ID") + private Long id; + + @Schema(description = "通话ID(呼叫系统唯一标识)") + private String callId; + + @Schema(description = "主叫号码") + private String callerNumber; + + @Schema(description = "主叫姓名") + private String callerName; + + @Schema(description = "被叫号码") + private String calleeNumber; + + @Schema(description = "被叫姓名") + private String calleeName; + + @Schema(description = "通话方向:IN-呼入, OUT-呼出") + private String callDirection; + + @Schema(description = "通话开始时间") + private LocalDateTime callStartTime; + + @Schema(description = "通话结束时间") + private LocalDateTime callEndTime; + + @Schema(description = "通话时长(秒)") + private Integer callDuration; + + @Schema(description = "通话状态:ANSWERED-已接通, MISSED-未接听, BUSY-忙线, REJECTED-拒接") + private String callStatus; + + @Schema(description = "录音文件URL") + private String recordingUrl; + + @Schema(description = "是否有录音:0-无, 1-有") + private Integer hasRecording; + + @Schema(description = "通话类型:NORMAL-普通, TRANSFER-转接, CONFERENCE-会议") + private String callType; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "逻辑删除:0-未删除, 1-已删除") + private Integer deleted; +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/domain/form/PhoneRecordQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/domain/form/PhoneRecordQueryForm.java new file mode 100644 index 0000000..9359a5e --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/domain/form/PhoneRecordQueryForm.java @@ -0,0 +1,48 @@ +package net.lab1024.sa.admin.module.business.phone.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +import java.time.LocalDateTime; + +/** + * 电话记录查询表单 + * + * @Author SmartFlow + * @Date 2026-06-09 + */ +@Data +@Schema(description = "电话记录查询表单") +public class PhoneRecordQueryForm extends PageParam { + + @Schema(description = "通话ID") + private String callId; + + @Schema(description = "主叫号码") + private String callerNumber; + + @Schema(description = "主叫姓名") + private String callerName; + + @Schema(description = "被叫号码") + private String calleeNumber; + + @Schema(description = "通话方向:IN-呼入, OUT-呼出") + private String callDirection; + + @Schema(description = "通话状态:ANSWERED-已接通, MISSED-未接听, BUSY-忙线, REJECTED-拒接") + private String callStatus; + + @Schema(description = "通话类型:NORMAL-普通, TRANSFER-转接, CONFERENCE-会议") + private String callType; + + @Schema(description = "开始时间") + private LocalDateTime startTime; + + @Schema(description = "结束时间") + private LocalDateTime endTime; + + @Schema(description = "是否有录音:0-无, 1-有") + private Integer hasRecording; +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/domain/vo/PhoneRecordVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/domain/vo/PhoneRecordVO.java new file mode 100644 index 0000000..78a90ef --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/domain/vo/PhoneRecordVO.java @@ -0,0 +1,74 @@ +package net.lab1024.sa.admin.module.business.phone.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 电话记录VO + * + * @Author SmartFlow + * @Date 2026-06-09 + */ +@Data +@Schema(description = "电话记录VO") +public class PhoneRecordVO { + + @Schema(description = "主键ID") + private Long id; + + @Schema(description = "通话ID") + private String callId; + + @Schema(description = "主叫号码") + private String callerNumber; + + @Schema(description = "主叫姓名") + private String callerName; + + @Schema(description = "被叫号码") + private String calleeNumber; + + @Schema(description = "被叫姓名") + private String calleeName; + + @Schema(description = "通话方向:IN-呼入, OUT-呼出") + private String callDirection; + + @Schema(description = "通话方向文本") + private String callDirectionText; + + @Schema(description = "通话开始时间") + private LocalDateTime callStartTime; + + @Schema(description = "通话结束时间") + private LocalDateTime callEndTime; + + @Schema(description = "通话时长(秒)") + private Integer callDuration; + + @Schema(description = "通话时长文本") + private String callDurationText; + + @Schema(description = "通话状态:ANSWERED-已接通, MISSED-未接听, BUSY-忙线, REJECTED-拒接") + private String callStatus; + + @Schema(description = "通话状态文本") + private String callStatusText; + + @Schema(description = "录音文件URL") + private String recordingUrl; + + @Schema(description = "是否有录音:0-无, 1-有") + private Integer hasRecording; + + @Schema(description = "通话类型:NORMAL-普通, TRANSFER-转接, CONFERENCE-会议") + private String callType; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "创建时间") + private LocalDateTime createTime; +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/service/PhoneService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/service/PhoneService.java new file mode 100644 index 0000000..c22a5c0 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/phone/service/PhoneService.java @@ -0,0 +1,141 @@ +package net.lab1024.sa.admin.module.business.phone.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.business.phone.dao.PhoneRecordDao; +import net.lab1024.sa.admin.module.business.phone.domain.entity.PhoneRecordEntity; +import net.lab1024.sa.admin.module.business.phone.domain.form.PhoneRecordQueryForm; +import net.lab1024.sa.admin.module.business.phone.domain.vo.PhoneRecordVO; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.config.PhoneWebSocketHandler; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 电话管理业务服务类 + * + * @Author SmartFlow + * @Date 2026-06-09 + */ +@Service +@Slf4j +public class PhoneService { + + @Resource + private PhoneRecordDao phoneRecordDao; + + @Resource + private PhoneWebSocketHandler phoneWebSocketHandler; + + /** + * 分页查询电话记录列表 + */ + public ResponseDTO> queryPhoneRecordPage(PhoneRecordQueryForm queryForm) { + Page queryPage = SmartPageUtil.convert2PageQuery(queryForm); + Page page = new Page<>(queryPage.getCurrent(), queryPage.getSize()); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + + if (StringUtils.isNotBlank(queryForm.getCallerNumber())) { + wrapper.like(PhoneRecordEntity::getCallerNumber, queryForm.getCallerNumber()); + } + if (StringUtils.isNotBlank(queryForm.getCallerName())) { + wrapper.like(PhoneRecordEntity::getCallerName, queryForm.getCallerName()); + } + if (StringUtils.isNotBlank(queryForm.getCallStatus())) { + wrapper.eq(PhoneRecordEntity::getCallStatus, queryForm.getCallStatus()); + } + if (queryForm.getStartTime() != null) { + wrapper.ge(PhoneRecordEntity::getCallStartTime, queryForm.getStartTime()); + } + if (queryForm.getEndTime() != null) { + wrapper.le(PhoneRecordEntity::getCallStartTime, queryForm.getEndTime()); + } + + wrapper.eq(PhoneRecordEntity::getDeleted, 0); + wrapper.orderByDesc(PhoneRecordEntity::getCallStartTime); + + phoneRecordDao.selectPage(page, wrapper); + List voList = SmartBeanUtil.copyList(page.getRecords(), PhoneRecordVO.class); + + voList.forEach(vo -> { + vo.setCallDirectionText("IN".equals(vo.getCallDirection()) ? "呼入" : "呼出"); + vo.setCallStatusText(getCallStatusText(vo.getCallStatus())); + vo.setCallDurationText(formatDuration(vo.getCallDuration())); + }); + + PageResult pageResult = SmartPageUtil.convert2PageResult(page, voList); + return ResponseDTO.ok(pageResult); + } + + /** + * 模拟接收来电(用于测试) + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO simulateIncomingCall(String callerNumber, String callerName, String agentId) { + String callId = "CALL-" + System.currentTimeMillis(); + + PhoneRecordEntity record = new PhoneRecordEntity(); + record.setId(System.currentTimeMillis()); + record.setCallId(callId); + record.setCallerNumber(callerNumber); + record.setCallerName(callerName); + record.setCallDirection("IN"); + record.setCallStartTime(LocalDateTime.now()); + record.setCallStatus("ANSWERED"); + record.setHasRecording(0); + record.setCreateTime(LocalDateTime.now()); + record.setUpdateTime(LocalDateTime.now()); + record.setDeleted(0); + + phoneRecordDao.insert(record); + + Map callData = new HashMap<>(); + callData.put("callId", callId); + callData.put("callerNumber", callerNumber); + callData.put("callerName", callerName); + callData.put("callStartTime", LocalDateTime.now().toString()); + callData.put("callDirection", "IN"); + + phoneWebSocketHandler.sendIncomingCall(agentId, callData); + + log.info("模拟来电成功 - 来电号码: {}, 来电姓名: {}, 坐席: {}", callerNumber, callerName, agentId); + + return ResponseDTO.ok(callId); + } + + private String getCallStatusText(String status) { + if (status == null) return ""; + switch (status) { + case "ANSWERED": return "已接"; + case "MISSED": return "未接"; + case "BUSY": return "忙线"; + case "REJECTED": return "拒接"; + default: return status; + } + } + + private String formatDuration(Integer seconds) { + if (seconds == null || seconds == 0) return "0秒"; + int hours = seconds / 3600; + int minutes = (seconds % 3600) / 60; + int secs = seconds % 60; + + StringBuilder sb = new StringBuilder(); + if (hours > 0) sb.append(hours).append("小时"); + if (minutes > 0) sb.append(minutes).append("分钟"); + if (secs > 0) sb.append(secs).append("秒"); + return sb.toString(); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/pom.xml b/smart-flow-api/sa-base/pom.xml index 71a1f77..1c916cd 100644 --- a/smart-flow-api/sa-base/pom.xml +++ b/smart-flow-api/sa-base/pom.xml @@ -308,7 +308,13 @@ tika-core + + + org.springframework.boot + spring-boot-starter-websocket + + - + \ No newline at end of file diff --git a/smart-flow-web/src/api/business/phone/phone-api.js b/smart-flow-web/src/api/business/phone/phone-api.js new file mode 100644 index 0000000..9cd97cc --- /dev/null +++ b/smart-flow-web/src/api/business/phone/phone-api.js @@ -0,0 +1,11 @@ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const phoneApi = { + queryPhoneRecordPage: (param) => { + return postRequest('/phone/record/queryPage', param); + }, + + simulateIncomingCall: (callerNumber, callerName, agentId) => { + return postRequest(`/phone/simulate/incomingCall?callerNumber=${callerNumber}&callerName=${callerName}&agentId=${agentId}`); + }, +}; \ No newline at end of file diff --git a/smart-flow-web/src/utils/phone-websocket.js b/smart-flow-web/src/utils/phone-websocket.js new file mode 100644 index 0000000..7486b49 --- /dev/null +++ b/smart-flow-web/src/utils/phone-websocket.js @@ -0,0 +1,151 @@ +import { message } from 'ant-design-vue'; + +class PhoneWebSocket { + constructor() { + this.ws = null; + this.agentId = null; + this.reconnectTimer = null; + this.heartbeatTimer = null; + this.reconnectCount = 0; + this.maxReconnectCount = 5; + this.reconnectInterval = 3000; + this.heartbeatInterval = 30000; + this.listeners = {}; + } + + connect(agentId) { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { + console.log('WebSocket已连接'); + return; + } + + this.agentId = agentId; + const baseUrl = import.meta.env.VITE_APP_API_URL || ''; + const wsUrl = baseUrl.replace('http', 'ws') + '/websocket/phone?agentId=' + agentId; + + try { + this.ws = new WebSocket(wsUrl); + + this.ws.onopen = () => { + console.log('WebSocket连接成功'); + this.reconnectCount = 0; + this.startHeartbeat(); + this.emit('connected'); + }; + + this.ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + console.log('收到WebSocket消息:', data); + + if (data.type === 'INCOMING_CALL') { + this.emit('incomingCall', data.data); + } else if (data.type === 'CALL_ENDED') { + this.emit('callEnded', data.callId); + } else { + this.emit('message', data); + } + } catch (error) { + console.error('解析WebSocket消息失败:', error); + } + }; + + this.ws.onerror = (error) => { + console.error('WebSocket错误:', error); + this.emit('error', error); + }; + + this.ws.onclose = (event) => { + console.log('WebSocket连接关闭:', event.code, event.reason); + this.stopHeartbeat(); + this.emit('disconnected'); + + if (event.code !== 1000) { + this.reconnect(); + } + }; + } catch (error) { + console.error('WebSocket连接失败:', error); + this.reconnect(); + } + } + + reconnect() { + if (this.reconnectCount >= this.maxReconnectCount) { + console.log('达到最大重连次数,停止重连'); + message.error('WebSocket连接失败,请刷新页面重试'); + return; + } + + this.reconnectCount++; + console.log(`准备第${this.reconnectCount}次重连...`); + + this.reconnectTimer = setTimeout(() => { + this.connect(this.agentId); + }, this.reconnectInterval); + } + + startHeartbeat() { + this.stopHeartbeat(); + this.heartbeatTimer = setInterval(() => { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify({ type: 'PING', timestamp: Date.now() })); + } + }, this.heartbeatInterval); + } + + stopHeartbeat() { + if (this.heartbeatTimer) { + clearInterval(this.heartbeatTimer); + this.heartbeatTimer = null; + } + } + + on(event, callback) { + if (!this.listeners[event]) { + this.listeners[event] = []; + } + this.listeners[event].push(callback); + } + + off(event, callback) { + if (this.listeners[event]) { + this.listeners[event] = this.listeners[event].filter(cb => cb !== callback); + } + } + + emit(event, data) { + if (this.listeners[event]) { + this.listeners[event].forEach(callback => { + try { + callback(data); + } catch (error) { + console.error(`执行${event}事件回调失败:`, error); + } + }); + } + } + + disconnect() { + this.stopHeartbeat(); + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + if (this.ws) { + this.ws.close(1000, '客户端主动关闭'); + this.ws = null; + } + this.listeners = {}; + } + + send(data) { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify(data)); + } else { + console.warn('WebSocket未连接,无法发送消息'); + } + } +} + +export const phoneWebSocket = new PhoneWebSocket(); \ No newline at end of file diff --git a/smart-flow-web/src/views/jwpy/myrx/desk/index.vue b/smart-flow-web/src/views/jwpy/myrx/desk/index.vue index bd0038d..79c4e76 100644 --- a/smart-flow-web/src/views/jwpy/myrx/desk/index.vue +++ b/smart-flow-web/src/views/jwpy/myrx/desk/index.vue @@ -1,33 +1,466 @@ \ No newline at end of file