WXL (wul)
2 天以前 d3c60e18b95b50751f8088fa2d23cd8ff7f173bc
src/components/CallButton/index.vue
@@ -2,6 +2,9 @@
  <div class="call-container">
    <div class="sip-status" :class="sipStatusClass">
      SIP状态: {{ sipStatus }}
      <span v-if="reconnectCount > 0" class="reconnect-info">
        (重连: {{ reconnectCount }}次)
      </span>
    </div>
    <!-- 状态显示 -->
@@ -11,11 +14,29 @@
    <!-- 呼叫按钮 -->
    <button
      :class="['call-btn', { calling: isCalling }]"
      :class="[
        'call-btn',
        {
          calling: isCalling,
          registering: isRegistering,
          reconnecting: isReconnecting,
        },
      ]"
      @click="startCall"
      :disabled="isCalling || sipStatus !== '已注册'"
      :disabled="isButtonDisabled"
    >
      <i v-if="isRegistering || isReconnecting" class="el-icon-loading"></i>
      {{ callButtonText }}
    </button>
    <!-- 手动重连按钮 -->
    <button
      v-if="showManualReconnect"
      class="reconnect-btn"
      @click="manualReconnect"
      :disabled="isRegistering"
    >
      手动重连
    </button>
    <!-- 挂断按钮 -->
@@ -28,6 +49,7 @@
<script>
import sipService from "@/utils/sipService";
import { CallsetState, CallgetList } from "@/api/AiCentre/index";
export default {
  props: {
@@ -37,18 +59,28 @@
    },
  },
  data() {
    const randomNum = Math.floor(Math.random() * 20) + 1000; // 内部定义
    const randomNum = Math.floor(Math.random() * 20) + 1000;
    return {
      isCalling: false,
      callStatus: "idle", // idle, calling, connected, ended
      isRegistering: true,
      isReconnecting: false, // 添加重连中状态
      randomNum: randomNum,
      randomID: null,
      orgname: localStorage.getItem("orgname"),
      callStatus: "idle",
      sipStatus: "未连接",
      sipStatusClass: "status-disconnected",
      reconnectCount: 0, // 重连次数
      lastActivityTime: null, // 最后活动时间
      heartbeatTimer: null, // 心跳定时器
      reconnectTimer: null, // 重连定时器
      maxReconnectAttempts: 5, // 最大重连尝试次数
      reconnectDelay: 5000, // 重连延迟(ms)
      sipConfig: {
        wsUrl: "wss://1192.170.66.107:7443",
        sipUri: `${randomNum}` + "@1192.170.66.107",
        wsUrl: "",
        sipUri: "",
        password: "Smartor@2023",
        displayName: "Web 小龙",
        // realm: "9.208.5.18:8090",
      },
    };
  },
@@ -64,42 +96,254 @@
    },
    countdownText() {
      if (this.sipStatus !== "已注册") return "";
      const { canCall, reason } = sipService.canMakeCall();
      if (!canCall && reason.includes("等待")) {
        return reason;
      }
      return "";
    },
    isButtonDisabled() {
      return (
        this.isCalling ||
        this.sipStatus !== "已注册" ||
        this.isRegistering ||
        this.isReconnecting
      );
    },
    callButtonText() {
      if (this.isRegistering) return "注册中...";
      if (this.isReconnecting) return "重连中...";
      return this.isCalling ? "通话中..." : "一键呼叫";
    },
    callStatusClass() {
      return `status-${this.callStatus}`;
    },
    callButtonText() {
      return this.isCalling ? "通话中..." : "一键呼叫";
    showManualReconnect() {
      return (
        !this.isCalling &&
        !this.isRegistering &&
        this.sipStatus !== "已注册" &&
        this.sipStatus !== "连接中"
      );
    },
  },
  mounted() {
    console.log('当前分机号',this.sipConfig);
  created() {
    if (
      this.orgname == "第一人民医院湖滨院区" ||
      this.orgname == "第一人民医院吴山院区"
    ) {
      this.sipConfig.password = "heskj@1234";
    } else {
      this.sipConfig.password = "Smartor@2023";
    }
  },
    sipService.init(this.sipConfig);
    sipService.onStatusChange = (status) => {
      this.sipStatus = status.text;
      this.sipStatusClass = `status-${status.type}`;
    };
    // 监听通话状态变化
    sipService.onCallStatusChange = (status) => {
      this.callStatus = status.type;
      this.isCalling = status.type === "calling" || status.type === "connected";
      // 通知父组件通话状态变化
      this.$emit("call-status-change", status);
    };
  async mounted() {
    const orgName = localStorage.getItem("orgname");
    if (orgName == "景宁畲族自治县人民医院") {
      return;
    }
    await this.CallgetList();
    this.isRegistering = true;
    this.initSipService();
    this.setupHeartbeat();
  },
  methods: {
    async initSipService() {
      try {
        // 初始化sipService
        sipService.init(this.sipConfig);
        // 设置状态回调
        sipService.onStatusChange = (status) => {
          this.sipStatus = status.text;
          this.sipStatusClass = `status-${status.type}`;
          // 处理各种状态
          if (status.type === "registered") {
            this.handleRegistered();
          } else if (
            status.type === "failed" ||
            status.type === "disconnected"
          ) {
            this.handleDisconnected();
          } else if (status.type === "connecting") {
            this.handleConnecting();
          }
        };
        // 监听通话状态变化
        sipService.onCallStatusChange = (status) => {
          this.callStatus = status.type;
          this.isCalling =
            status.type === "calling" || status.type === "connected";
          this.updateLastActivityTime(); // 通话状态变化时更新活动时间
          this.$emit("call-status-change", status);
        };
        // 设置超时处理
        this.setupRegistrationTimeout();
      } catch (error) {
        console.error("SIP服务初始化失败:", error);
        this.handleDisconnected();
      }
    },
    handleRegistered() {
      console.log("SIP注册成功");
      this.isRegistering = false;
      this.isReconnecting = false;
      this.reconnectCount = 0; // 重置重连计数
      this.updateLastActivityTime();
      this.startCallsetState();
      // 清除重连定时器
      if (this.reconnectTimer) {
        clearTimeout(this.reconnectTimer);
        this.reconnectTimer = null;
      }
    },
    handleDisconnected() {
      console.log("SIP连接断开");
      this.isRegistering = false;
      this.overCallsetState();
      // 如果不是通话中断开,尝试重连
      if (!this.isCalling) {
        this.scheduleReconnect();
      }
    },
    handleConnecting() {
      this.isReconnecting = true;
    },
    setupRegistrationTimeout() {
      setTimeout(() => {
        if (this.isRegistering && this.sipStatus !== "已注册") {
          this.isRegistering = false;
          this.$message.warning("SIP注册超时,正在尝试重连...");
          this.scheduleReconnect();
        }
      }, 10000);
    },
    // 设置心跳检测
    setupHeartbeat() {
      this.heartbeatTimer = setInterval(() => {
        this.checkConnection();
      }, 60000); // 每30秒检查一次连接
    },
    // 检查连接状态
    async checkConnection() {
      // 如果正在注册、重连或通话中,不检查
      if (this.isRegistering || this.isReconnecting || this.isCalling) {
        return;
      }
      // 检查SIP连接状态
      if (sipService && sipService.ua) {
        const isConnected = sipService.ua.isConnected();
        const isRegistered = sipService.ua.isRegistered();
        if (!isConnected || !isRegistered) {
          console.log("心跳检测: 连接异常,尝试重连");
          await this.reconnectSip();
        } else {
          console.log("心跳检测: 连接正常");
          this.updateLastActivityTime();
        }
      } else {
        console.log("心跳检测: UA不存在,尝试重新初始化");
        await this.reconnectSip();
      }
    },
    // 计划重连
    scheduleReconnect() {
      if (this.reconnectTimer || this.isReconnecting) {
        return;
      }
      if (this.reconnectCount >= this.maxReconnectAttempts) {
        console.log("达到最大重连次数,停止重连");
        this.$message.error("SIP连接失败,请刷新页面重试");
        return;
      }
      this.reconnectCount++;
      const delay = Math.min(
        this.reconnectDelay * Math.pow(1.5, this.reconnectCount - 1),
        30000
      );
      console.log(`计划在${delay}ms后重连,第${this.reconnectCount}次尝试`);
      this.reconnectTimer = setTimeout(() => {
        this.reconnectSip();
      }, delay);
    },
    // 重新连接SIP
    async reconnectSip() {
      if (this.isReconnecting || this.isRegistering) {
        return;
      }
      console.log("开始重连SIP服务...");
      this.isReconnecting = true;
      try {
        // 清理现有连接
        this.cleanupSipConnection();
        // 等待一段时间
        await new Promise((resolve) => setTimeout(resolve, 1000));
        // 重新初始化
        await this.CallgetList(); // 重新获取分机号
        this.initSipService();
      } catch (error) {
        console.error("重连失败:", error);
        this.isReconnecting = false;
        this.scheduleReconnect(); // 失败后继续重试
      } finally {
        if (this.reconnectTimer) {
          clearTimeout(this.reconnectTimer);
          this.reconnectTimer = null;
        }
      }
    },
    // 清理SIP连接
    cleanupSipConnection() {
      if (sipService && sipService.ua) {
        try {
          sipService.ua.stop();
          sipService.ua.unregister();
        } catch (e) {
          console.warn("清理SIP连接时出错:", e);
        }
      }
    },
    // 手动重连
    async manualReconnect() {
      this.reconnectCount = 0; // 重置重连计数
      await this.reconnectSip();
    },
    // 更新最后活动时间
    updateLastActivityTime() {
      this.lastActivityTime = Date.now();
    },
    async startCall() {
      if (!this.phoneNumber) {
        this.$message.error("请输入电话号码");
        this.$message.warning("请输入电话号码");
        return;
      }
@@ -107,37 +351,86 @@
        // 先检查是否可以呼叫
        const { canCall, reason } = sipService.canMakeCall();
        if (!canCall) {
          const { canCall, reason } = sipService.canMakeCall();
          //this.$message.warning(reason);
          //return;
          // 可选: 可以显示提示
        }
        this.callStatus = "calling";
        this.isCalling = true;
        console.log("开始呼叫:", sipService);
        await sipService.makeCall("0"+this.phoneNumber);
        await sipService.makeCall(this.phoneNumber);
      } catch (error) {
        let registrationTime = Date.now(); // 记录注销成功时间
        console.log(registrationTime, "呼叫失败时间");
        console.error("呼叫失败1:", error);
        // this.callStatus = "ended";
        // this.isCalling = false;
        //this.$message.error(`呼叫失败: ${error.message}`);
          try {
          // 先检查是否可以呼叫
        if (
          error.message.includes("Canceled") ||
          error.message.includes("未注册")
        ) {
          console.warn("呼叫因页面离开或未注册而取消,不重试");
          this.callStatus = "ended";
          this.isCalling = false;
          return;
        }
        try {
          // 尝试加0再次呼叫
          const { canCall, reason } = sipService.canMakeCall();
          if (!canCall) {
            const { canCall, reason } = sipService.canMakeCall();
            // 可选处理
          }
          this.callStatus = "calling";
          this.isCalling = true;
          console.log("开始呼叫:", sipService);
          console.log("尝试加0再次呼叫:", sipService);
          await sipService.makeCall("0"+this.phoneNumber);
          await sipService.makeCall("0" + this.phoneNumber);
        } catch (error) {
           this.callStatus = "ended";
           this.isCalling = false;
          this.callStatus = "ended";
          this.isCalling = false;
          this.$message.warning("当前呼叫占线中,请稍后再拨");
        }
      }
    },
    // 查询可用分机号
    async CallgetList() {
      try {
        const res = await CallgetList();
        this.randomNum = res.data[0].tel;
        this.randomID = res.data[0].id;
        const orgName = localStorage.getItem("orgname");
        if (orgName == "丽水市中医院") {
          this.sipConfig.sipUri = `${this.randomNum}@192.168.10.124`;
        } else if (orgName == "龙泉市人民医院") {
          this.sipConfig.sipUri = `${this.randomNum}@10.10.0.220`;
        } else if (
          orgName == "第一人民医院湖滨院区" ||
          orgName == "第一人民医院吴山院区"
        ) {
          this.sipConfig.sipUri = `${this.randomNum}@192.169.129.198`;
        }
      } catch (error) {
        console.error("获取分机号失败:", error);
        throw error; // 抛出错误以便上层处理
      }
    },
    async startCallsetState() {
      try {
        await CallsetState({ id: this.randomID, state: 1 });
        console.log("分机号状态更新为使用中");
      } catch (error) {
        console.error("更新分机号状态失败:", error);
      }
    },
    async overCallsetState() {
      try {
        if (this.randomID) {
          await CallsetState({ id: this.randomID, state: 0 });
          console.log("分机号状态更新为可用");
        }
      } catch (error) {
        console.error("释放分机号失败:", error);
      }
    },
@@ -146,11 +439,43 @@
      this.callStatus = "ended";
      this.isCalling = false;
    },
    cleanupResources() {
      // 清除所有定时器
      if (this.heartbeatTimer) {
        clearInterval(this.heartbeatTimer);
        this.heartbeatTimer = null;
      }
      if (this.reconnectTimer) {
        clearTimeout(this.reconnectTimer);
        this.reconnectTimer = null;
      }
      // 结束通话
      if (this.isCalling) {
        sipService.endCall();
      }
      // 释放分机号
      this.overCallsetState();
      // 断开 SIP 连接
      this.cleanupSipConnection();
    },
  },
  beforeUnmount() {
    if (this.isCalling) {
      this.endCall(); // 内部设置了 isManualEnd
    }
    // 其他清理(如定时器)...
    this.cleanupResources(); // 但注意不要重复清理定时器,可优化判断
  },
};
</script>
<style scoped>
/* 保持原有样式不变,只添加新样式 */
.call-container {
  display: flex;
  flex-direction: column;
@@ -162,25 +487,93 @@
  border-radius: 8px;
}
input {
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
.reconnect-info {
  font-size: 12px;
  color: #666;
  margin-left: 5px;
}
.call-btn {
.reconnect-btn {
  padding: 8px 12px;
  background-color: #ff9800;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 12px;
}
.reconnect-btn:hover:not(:disabled) {
  background-color: #f57c00;
}
.reconnect-btn:disabled {
  background-color: #cccccc;
  cursor: not-allowed;
}
.call-btn.reconnecting {
  background-color: #ff9800;
}
.call-btn:hover:not(:disabled) {
  background-color: #45a049;
}
.call-btn:disabled {
  background-color: #cccccc;
  cursor: not-allowed;
}
.call-btn.calling {
  background-color: #2196f3;
}
.call-btn.registering,
.call-btn.reconnecting {
  position: relative;
}
.end-call-btn {
  padding: 10px;
  background-color: #4caf50;
  background-color: #f44336;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
.end-call-btn:hover {
  background-color: #d32f2f;
}
/* 状态样式保持不变 */
.sip-status,
.call-status {
  padding: 8px;
  margin: 10px 0;
  margin-bottom: 10px;
  border-radius: 4px;
  text-align: center;
}
.status-disconnected {
  background-color: #ffebee;
  color: #c62828;
}
.status-connecting {
  background-color: #fff8e1;
  color: #ff8f00;
}
.status-registered {
  background-color: #e8f5e9;
  color: #2e7d32;
}
.status-failed {
  background-color: #ffebee;
  color: #c62828;
}
.status-idle {
@@ -199,132 +592,6 @@
}
.status-ended {
  background-color: #ffebee;
  color: #c62828;
}
/* 原有样式保持不变 */
.call-container {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 300px;
  margin: 0 auto;
  padding: 20px;
  border: 1px solid #eee;
  border-radius: 8px;
}
.call-btn {
  padding: 10px;
  background-color: #4caf50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
.call-btn:hover:not(:disabled) {
  background-color: #45a049;
}
.call-btn:disabled {
  background-color: #cccccc;
  cursor: not-allowed;
}
.call-btn.calling {
  background-color: #2196f3;
}
.end-call-btn {
  padding: 10px;
  background-color: #f44336;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
.end-call-btn:hover {
  background-color: #d32f2f;
}
.sip-status {
  padding: 8px;
  margin-bottom: 10px;
  border-radius: 4px;
  text-align: center;
}
.status-disconnected {
  background-color: #ffebee;
  color: #c62828;
}
.status-connecting {
  background-color: #fff8e1;
  color: #ff8f00;
}
.status-registered {
  background-color: #e8f5e9;
  color: #2e7d32;
}
.status-failed {
  background-color: #ffebee;
  color: #c62828;
}
.call-btn:hover {
  background-color: #45a049;
}
.call-btn.calling {
  background-color: #2196f3;
}
.end-call-btn {
  padding: 10px;
  background-color: #f44336;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
.end-call-btn:hover {
  background-color: #d32f2f;
}
.call-status {
  margin-top: 10px;
  font-size: 14px;
  color: #666;
}
.sip-status {
  padding: 8px;
  margin-bottom: 10px;
  border-radius: 4px;
  text-align: center;
}
.status-disconnected {
  background-color: #ffebee;
  color: #c62828;
}
.status-connecting {
  background-color: #fff8e1;
  color: #ff8f00;
}
.status-registered {
  background-color: #e8f5e9;
  color: #2e7d32;
}
.status-failed {
  background-color: #ffebee;
  color: #c62828;
}