WXL (wul)
3 天以前 8dfc9189443d7baf2e73d99a73e1b20eefba366e
测试完成
已删除1个文件
已重命名1个文件
已修改3个文件
287 ■■■■■ 文件已修改
dist (2).zip 补丁 | 查看 | 原始文档 | blame | 历史
dist.zip 补丁 | 查看 | 原始文档 | blame | 历史
lishui (2).zip 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/sipService.js 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/satisfaction.vue 105 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
dist (2).zip
Binary files differ
dist.zip
Binary files differ
lishui (2).zip
Binary files differ
src/utils/sipService.js
@@ -29,7 +29,7 @@
  }
  // 获取医院配置方法
  getHospitalConfig() {
    const orgName=localStorage.getItem("orgname");
    const orgName = localStorage.getItem("orgname");
    return HOSPITAL_CONFIG[orgName] || HOSPITAL_CONFIG.default;
  }
  init(baseConfig) {
@@ -39,7 +39,7 @@
      // 根据机构名称获取对应的服务器配置
      const hospitalConfig = this.getHospitalConfig(orgName);
console.log(hospitalConfig,'88');
      console.log(hospitalConfig, "88");
      // 合并配置
      this.currentConfig = {
@@ -52,7 +52,7 @@
      );
      this.updateStatus("connecting", "连接中...");
console.log(baseConfig.sipUri,'baseConfig.sipUri');
      console.log(baseConfig.sipUri, "baseConfig.sipUri");
      this.ua = new JsSIP.UA({
        sockets: [new JsSIP.WebSocketInterface(this.currentConfig.wsUrl)],
@@ -190,31 +190,181 @@
    });
  }
  //   normalizeSDP(offer) {
  //     let sdp = offer.sdp;
  //  console.log("原始SDP:", sdp); // 调试用,捕获原始SDP
  //     // 标准化SDP
  //     sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
  //     sdp = sdp.replace(
  //       /m=audio \d+.*\r\n/,
  //       "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n"
  //     );
  //     // 确保包含基本编解码器
  //     if (!sdp.includes("PCMU/8000")) sdp += "a=rtpmap:0 PCMU/8000\r\n";
  //     if (!sdp.includes("PCMA/8000")) sdp += "a=rtpmap:8 PCMA/8000\r\n";
  //     // 添加必要属性
  //     sdp += "a=rtcp-mux\r\n";
  //     sdp += "a=sendrecv\r\n";
  //     console.log("标准化后的SDP:", sdp);
  //     return new RTCSessionDescription({
  //       type: offer.type,
  //       sdp: sdp,
  //     });
  //   }
  // 在 SipService 类中新增方法,用于获取针对特定服务器的SDP处理策略
  getSDPNormalizationStrategy(orgName) {
    const strategies = {
      龙泉市人民医院: "conservative", // 保守策略:最小化修改,优先兼容
      丽水市中医院: "aggressive", // 激进策略:保持原有强标准化逻辑
      // 可以为其他机构添加更多策略
    };
    return strategies[orgName] || "moderate"; // 默认策略
  }
  /**
   * 标准化SDP Offer - 修复龙泉市人民医院488错误
   * 核心思路:从“强制覆盖”改为“智能修补”,针对不同服务器使用差异化策略
   */
  normalizeSDP(offer) {
    const orgName = localStorage.getItem("orgname");
    const strategy = this.getSDPNormalizationStrategy(orgName);
    let sdp = offer.sdp;
    // 标准化SDP
    sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
    sdp = sdp.replace(
      /m=audio \d+.*\r\n/,
      "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n"
    );
    console.log(`[SDP标准化] 机构: ${orgName}, 策略: ${strategy}`);
    console.log("[SDP标准化] 原始SDP:", sdp);
    // 确保包含基本编解码器
    if (!sdp.includes("PCMU/8000")) sdp += "a=rtpmap:0 PCMU/8000\r\n";
    if (!sdp.includes("PCMA/8000")) sdp += "a=rtpmap:8 PCMA/8000\r\n";
    if (strategy === "conservative") {
      // ==================== 保守策略:针对龙泉市人民医院等严格服务器 ====================
      // 原则:除非必要,否则不修改原有SDP结构,仅添加缺失的关键属性
    // 添加必要属性
    sdp += "a=rtcp-mux\r\n";
    sdp += "a=sendrecv\r\n";
      // 1. 谨慎处理连接地址:仅在地址是明显内网地址时才修改
      const privateIPRegex =
        /c=IN IP4 (192\.168|10\.|172\.(1[6-9]|2[0-9]|3[0-1]))/;
      if (privateIPRegex.test(sdp)) {
        sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
        console.log("[SDP标准化] 已修改连接地址为 0.0.0.0");
      }
    console.log("标准化后的SDP:", sdp);
      // 2. 保持媒体行原样,不强制修改端口和协议
      // sdp = sdp.replace(/m=audio \d+.*\r\n/, "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n");
      // 3. 智能添加基础编解码器映射(仅在缺失时添加)
      const codecMappings = [
        { pt: 0, name: "PCMU/8000" },
        { pt: 8, name: "PCMA/8000" },
      ];
      codecMappings.forEach((codec) => {
        const rtpmapPattern = `a=rtpmap:${codec.pt} ${codec.name}`;
        const payloadPattern = ` ${codec.pt} `;
        // 只有当SDP中包含该负载类型但缺少详细映射时才添加
        if (sdp.includes(payloadPattern) && !sdp.includes(rtpmapPattern)) {
          sdp += `${rtpmapPattern}\r\n`;
          console.log(`[SDP标准化] 已添加编解码器映射: ${rtpmapPattern}`);
        }
      });
      // 4. 条件性添加必要属性(避免重复)
      const essentialAttributes = [
        { attr: "a=rtcp-mux", desc: "RTCP复用" },
        { attr: "a=sendrecv", desc: "双向媒体流" },
      ];
      essentialAttributes.forEach((item) => {
        if (!sdp.includes(item.attr)) {
          sdp += `${item.attr}\r\n`;
          console.log(`[SDP标准化] 已添加属性: ${item.desc}`);
        }
      });
    } else if (strategy === "aggressive") {
      // ==================== 激进策略:保持原有逻辑 ====================
      sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
      sdp = sdp.replace(
        /m=audio \d+.*\r\n/,
        "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n"
      );
      // 确保包含基础编解码器
      if (!sdp.includes("PCMU/8000")) sdp += "a=rtpmap:0 PCMU/8000\r\n";
      if (!sdp.includes("PCMA/8000")) sdp += "a=rtpmap:8 PCMA/8000\r\n";
      // 添加通用属性
      sdp += "a=rtcp-mux\r\n";
      sdp += "a=sendrecv\r\n";
    } else {
      // ==================== 默认策略:平衡方案 ====================
      // 适度修改,兼顾兼容性和功能性
      sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
      // 仅在媒体行格式明显异常时修改
      if (!sdp.match(/m=audio \d+ RTP\/AVP/)) {
        sdp = sdp.replace(
          /m=audio \d+.*\r\n/,
          "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n"
        );
      }
      // 智能添加缺失的属性
      if (!sdp.includes("a=rtcp-mux")) sdp += "a=rtcp-mux\r\n";
      if (!sdp.includes("a=sendrecv")) sdp += "a=sendrecv\r\n";
    }
    console.log("[SDP标准化] 标准化后SDP:", sdp);
    return new RTCSessionDescription({
      type: offer.type,
      sdp: sdp,
    });
  }
  /**
   * 增强的SDP调试方法 - 用于对比分析
   */
  debugSDPComparison(originalOffer, normalizedOffer, context) {
    console.group(`[SDP调试] ${context}`);
    console.log(
      "原始SDP媒体行:",
      originalOffer.sdp.match(/m=audio.*\r\n/)?.[0] || "未找到"
    );
    console.log(
      "标准化后媒体行:",
      normalizedOffer.sdp.match(/m=audio.*\r\n/)?.[0] || "未找到"
    );
    console.log(
      "原始编解码器列表:",
      originalOffer.sdp.match(/a=rtpmap:\d+.*\r\n/g) || []
    );
    console.log(
      "标准化后编解码器列表:",
      normalizedOffer.sdp.match(/a=rtpmap:\d+.*\r\n/g) || []
    );
    console.groupEnd();
  }
  // 在 setupPeerConnection 方法中集成调试功能
  setupPeerConnection(session) {
    session.on("peerconnection", (pc) => {
      const originalCreateOffer = pc.createOffer.bind(pc);
      pc.createOffer = async (offerOptions) => {
        try {
          const offer = await originalCreateOffer(offerOptions);
          const normalizedOffer = this.normalizeSDP(offer);
          // 调试信息输出
          this.debugSDPComparison(offer, normalizedOffer, "Offer创建阶段");
          return normalizedOffer;
        } catch (error) {
          console.error("创建Offer失败:", error);
          throw error;
        }
      };
    });
  }
  handleCallFailure(e, reject) {
    if (e.response?.status_code === 422) {
      const serverMinSE = e.response.headers["Min-SE"]?.[0]?.raw || "未知";
src/views/sfstatistics/percentage/satisfaction.vue
@@ -510,22 +510,33 @@
    <!-- 单科室统计详情 -->
    <el-dialog :visible.sync="topicVisible" width="45%">
      <div class="topicdia">
        <div class="top-text">{{ topicvalue.name }}</div>
        <div class="top-mintext">随访完成数{{ topicvalue.number }}</div>
        <div class="top-text">
          {{ topicvalue.name }}<span>满意度指标详情</span>
        </div>
        <div style="overflow-x: hidden; overflow-y: auto; max-height: 65vh">
          <div class="ttaabbcc" v-for="item in topiclist" :key="item.name">
          <div
            class="ttaabbcc"
            v-for="(item, index) in topiclist"
            :key="item.name"
          >
            <div class="describe">
              第{{ item.number }}题: {{ item.name }}?<span
                >[{{ item.type == 1 ? "单选题" : "多选题" }}]</span
              第{{ index }}题: {{ item.scriptContent }}?<span
                >[{{ item.scriptType == 1 ? "单选题" : "多选题" }}]</span
              >
            </div>
            <div>
              <el-table :data="tableData" style="width: 100%">
                <el-table-column prop="date" label="问题选项">
              <el-table :data="item.details" style="width: 100%">
                <el-table-column prop="optionText" label="问题选项">
                </el-table-column>
                <el-table-column prop="name" label="选择人数">
                <el-table-column prop="completedQuantity" label="选择人数">
                </el-table-column>
                <el-table-column prop="address" label="比例"> </el-table-column>
                <el-table-column prop="chosenPercentage" label="比例">
                   <template slot-scope="scope">
                  <span class="button-zx"
                    >{{ (Number(scope.row.chosenPercentage) * 100).toFixed(2) }}%</span
                  >
                </template>
                </el-table-column>
              </el-table>
            </div>
          </div>
@@ -892,8 +903,12 @@
        ...this.queryParams,
      };
      if (this.queryParams.statisticaltype == 1) {
        this.topicvalue.name = row.leavehospitaldistrictname;
        params.leavehospitaldistrictcodes = [row.leavehospitaldistrictcode];
      } else {
        this.topicvalue.name = row.deptname;
        params.deptcodes = [row.deptcode];
      }
@@ -902,8 +917,7 @@
      delete params.deptcodes.all;
      getSfStatisticsJoyInfo(params).then((response) => {
        console.log(response);
        this.total = response.total;
        this.userList = response.data;
        this.topiclist = response.data;
      });
    },
    // 添加/修改标签
@@ -1446,6 +1460,75 @@
    height: 400px;
  }
}
.topicdia {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  color: #333; /* 主文字色 */
}
/* 头部标题样式 */
.top-text {
  font-size: 18px;
  font-weight: 600;
  padding-bottom: 16px;
  margin-bottom: 20px;
  border-bottom: 1px solid #e8e8e8; /* 纤细的分隔线 */
  color: #1f2d3d; /* 深色标题 */
}
.top-text span {
  font-size: 14px;
  font-weight: normal;
  color: #666; /* 副标题颜色稍浅 */
  margin-left: 10px;
}
/* 题目容器样式 */
.ttaabbcc {
  background: #fafafa; /* 非常浅的灰色背景 */
  border-radius: 6px;
  padding: 16px;
  margin-bottom: 20px;
  border-left: 4px solid #4794c5; /* 左侧装饰色条,增加层次感 */
}
/* 题目描述样式 */
.describe {
  font-size: 15px;
  line-height: 1.6;
  margin-bottom: 12px;
  color: #1f2d3d;
}
.describe span {
  font-size: 13px;
  color: #999; /* 题型提示信息颜色更浅 */
  font-style: italic;
  margin-left: 8px;
}
/* 表格整体样式调整 */
.ttaabbcc .el-table {
  border-radius: 4px;
  overflow: hidden;
  font-size: 14px;
}
/* 表头样式 */
.ttaabbcc .el-table th {
  background-color: #f1f5f9; /* 浅蓝色表头背景 */
  color: #333;
  font-weight: 600;
}
/* 单元格样式 */
.ttaabbcc .el-table td {
  border-bottom: 1px solid #f0f0f0; /* 纤细的行分隔线 */
  padding: 12px 0;
}
/* 比例数据样式 */
.button-zx {
  color: #4794c5; /* 使用与主题呼应的蓝色 */
  font-weight: 500;
}
::v-deep.el-tabs--left,
.el-tabs--right {
  overflow: hidden;