WXL
2 天以前 f06ac3402687f19065bd5ae738f9a8e95dad7eaa
测试完成
已删除2个文件
已修改2个文件
176 ■■■■■ 文件已修改
dist (2).zip 补丁 | 查看 | 原始文档 | blame | 历史
dist.zip 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/sipService.js 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
dist (2).zip
Binary files differ
dist.zip
Binary files differ
src/utils/sipService.js
@@ -1,118 +1,172 @@
import JsSIP from 'jssip'
import JsSIP from "jssip";
class SipService {
  constructor() {
    this.ua = null
    this.currentSession = null
    this.onStatusChange = null // 状态变化回调
    this.ua = null;
    this.currentSession = null;
    this.onStatusChange = null; // 状态变化回调
  }
  // 初始化SIP客户端
  init(config) {
    try {
      this.updateStatus('connecting', '连接中...')
      this.updateStatus("connecting", "连接中...");
      this.ua = new JsSIP.UA({
        sockets: [new JsSIP.WebSocketInterface(config.wsUrl)],
        uri: config.sipUri,
        password: config.password,
        display_name: config.displayName,
        iceservers:[],
        // realm: config.realm,
        register: true,
        session_expires:180,
        sessionTimersExpires: 300, // 设置 Session-Expires=120(必须 >= Min-SE)
        extraHeaders: [
          "Min-SE: 120", // 可选:显式告诉服务器你支持的最小值
        ],
        register_expires: 300, // 注册有效期(秒)
        connection_recovery_min_interval: 2, // 最小重连间隔
        connection_recovery_max_interval: 30 // 最大重连间隔
      })
        connection_recovery_max_interval: 30, // 最大重连间隔
      });
      this.ua.start()
      this.ua.start();
      // 注册事件监听
      this.ua.on('registered', () => {
        this.updateStatus('registered', '已注册')
      })
      this.ua.on("registered", () => {
        this.updateStatus("registered", "已注册");
      });
      this.ua.on('registrationFailed', (e) => {
        this.updateStatus('failed', `注册失败: ${e.cause}`)
      })
      this.ua.on("registrationFailed", (e) => {
        this.updateStatus("failed", `注册失败: ${e.cause}`);
      });
      this.ua.on('disconnected', () => {
        this.updateStatus('disconnected', '连接断开')
      })
      this.ua.on("disconnected", () => {
        this.updateStatus("disconnected", "连接断开");
      });
      this.ua.on('connected', () => {
        this.updateStatus('connecting', '重新连接中...')
      })
      this.ua.on("connected", () => {
        this.updateStatus("connecting", "重新连接中...");
      });
      // 监听来电
      this.ua.on('newRTCSession', (data) => {
        this.handleIncomingCall(data.session)
      })
      this.ua.on("newRTCSession", (data) => {
        this.handleIncomingCall(data.session);
      });
    } catch (error) {
      this.updateStatus('failed', `初始化失败: ${error.message}`)
      console.error('SIP初始化失败:', error)
      this.updateStatus("failed", `初始化失败: ${error.message}`);
      console.error("SIP初始化失败:", error);
    }
  }
  handleIncomingCall(session) {
    if (session.direction === "incoming") {
      console.log("来电:", session.remote_identity.uri.toString());
      // 可以在这里触发 UI 通知
      if (this.onIncomingCall) {
        this.onIncomingCall(session);
      }
    }
  }
  // 更新状态并通知UI
  updateStatus(type, text) {
    console.log(`SIP状态更新: ${type} - ${text}`)
    console.log(`SIP状态更新: ${type} - ${text}`);
    if (this.onStatusChange) {
      this.onStatusChange({ type, text })
      this.onStatusChange({ type, text });
    }
  }
  // 一键拨号 - 增加注册状态检查
  makeCall(targetNumber) {
    if (!this.ua) {
      throw new Error('SIP客户端未初始化')
      throw new Error("SIP客户端未初始化");
    }
    if (!this.ua.isRegistered()) {
      throw new Error('SIP未注册,无法呼叫')
      throw new Error("SIP未注册,无法呼叫");
    }
    const options = {
      sessionTimers: true,
      sessionTimersExpires: 300,
      extraHeaders: [
        "Min-SE: 120",
        "Route: <sip:@192.168.100.6>",
        "Accept: application/sdp",
        "Supported: replaces, timer",
        "Allow: INVITE, ACK, BYE, CANCEL, OPTIONS",
      ],
      eventHandlers: {
        progress: (e) => console.log('呼叫中...'),
        failed: (e) => console.error('呼叫失败:', e),
        ended: (e) => console.log('通话结束'),
        confirmed: (e) => console.log('通话已接通')
        progress: (e) => console.log("呼叫中..."),
        failed: (e) => console.error("呼叫失败:", e),
        ended: (e) => console.log("通话结束"),
        confirmed: (e) => console.log("通话已接通"),
      },
      mediaConstraints: { audio: true, video: false },
      rtcOfferConstraints: { offerToReceiveAudio: 1 }
    }
      mediaConstraints: {
        audio: true,
        video: false,
      },
      rtcOfferConstraints: {
        offerToReceiveAudio: 1,
        offerToReceiveVideo: 0,
        mandatory: {
          OfferToReceiveAudio: true,
          OfferToReceiveVideo: false,
        },
      },
      pcConfig: {
        iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
        iceTransportPolicy: "all",
        bundlePolicy: "balanced",
        rtcpMuxPolicy: "require",
        codecs: {
          audio: [
            { name: "PCMU", clockRate: 8000, payloadType: 0 },
            { name: "PCMA", clockRate: 8000, payloadType: 8 },
          ],
          video: [],
        },
      },
    };
    this.currentSession = this.ua.call(`sip:${targetNumber}@192.168.100.6`, options)
    this.setupAudio(this.currentSession)
    this.currentSession = this.ua.call(
      `sip:${targetNumber}@192.168.100.6`,
      options
    );
    // 在会话创建后修改 SDP
    this.currentSession.on("peerconnection", (pc) => {
      pc.createOffer = (offerOptions) => {
        return RTCPeerConnection.prototype.createOffer
          .call(pc, offerOptions)
          .then((offer) => {
            const modifiedSdp = offer.sdp
              .replace(/c=IN IP4 192\.168\.100\.10/g, "c=IN IP4 192.168.100.6")
              .replace(/m=audio \d+ RTP\/AVP.*/, "m=audio 7078 RTP/AVP 0 8");
            return new RTCSessionDescription({
              type: "offer",
              sdp: modifiedSdp,
            });
          });
      };
    });
    this.setupAudio(this.currentSession);
  }
  setupAudio(session) {
    session.connection.addEventListener("addstream", (e) => {
      const audioElement = document.getElementById("remoteAudio");
      if (audioElement) {
        audioElement.srcObject = e.stream;
      }
    });
  }
  // 挂断当前通话
  endCall() {
    if (this.currentSession) {
      this.currentSession.terminate()
      this.currentSession = null
      this.currentSession.terminate();
      this.currentSession = null;
    }
  }
  // 处理音频流
  setupAudio(session) {
    session.connection.addEventListener('addstream', (e) => {
      const audioElement = document.getElementById('remoteAudio')
      if (audioElement) {
        audioElement.srcObject = e.stream
      }
    })
  }
  // 处理来电
  handleIncomingCall(session) {
    if (session.direction === 'incoming') {
      console.log('来电:', session.remote_identity.uri.toString())
      // 这里可以触发UI通知
    }
  }
}
export default new SipService()
export default new SipService();
src/views/sfstatistics/percentage/index.vue
@@ -429,7 +429,7 @@
  },
];
export default {
  name: "questionnaire",
  name: "Percentage",
  dicts: ["sys_normal_disable", "sys_user_sex"],
  components: { Treeselect },
  data() {