# SipCallPhoneDialog 一键外呼组件（公共组件）

> 文件：`@/components/sipCall/sipCallPhoneDialog.vue`  
> 本文档面向业务页面接入，参数统一使用 `sip` 前缀，避免与调用页本地字段冲突。

---

## 1. 能力概览

打开弹窗后按步骤执行：

1. **解析号码**：加密号解密 / 或直接使用明文号
2. **选择线路**：展示可用网关列表，**必须选择并确认后**才会继续
3. **连接座席（IPCC）**：按所选线路获取 token 并连接
4. **注册分机（JsSIP）**
5. **发起外呼**
6. **挂断后**：延迟 **5 秒**同步通话记录，并做防重复同步

高频安全点：

- 初始化/确认线路/拨号有互斥锁，避免连点重复发起
- 通话中不可点遮罩关闭；关闭时会取消初始化并清理连接
- 同一 `uuid` 同步使用队列 + `syncedUuids` / `syncingUuids` 去重，不会重复同步

---

## 2. Props（全部 sip 前缀）

| Prop（模板写法） | 类型 | 必填 | 默认 | 说明 |
|---|---|:---:|---|---|
| `sip-visible.sync` | Boolean | 是 | `false` | 弹窗显隐。必须 `.sync`，对应事件 `update:sipVisible` |
| `sip-encrypted-phone` | String | 否* | `''` | 加密手机号。与明文二选一，**优先使用加密号** |
| `sip-plain-phone` | String | 否* | `''` | 明文手机号。无加密号时使用，会跳过解密步骤 |
| `sip-dial-type` | String | 否 | `'audio'` | `'audio'` / `'video'` |
| `sip-record-id` | String/Number | 否 | `''` | 业务关联 ID（如订单号）。挂断后通过事件回传 |

> \* `sipEncryptedPhone` 与 `sipPlainPhone` **至少传一个**，否则无法拨号。

---

## 3. Events

| 事件名 | 参数 | 说明 |
|---|---|---|
| `update:sipVisible` | Boolean | 弹窗关闭/打开时触发（`.sync` 自动处理） |
| `sip-call-ended` | `sipRecordId` | 通话结束后触发，回传业务 ID |

---

## 4. 推荐接入示例（问诊详情 / 业务页）

```vue
<template>
  <div>
    <el-button type="text" @click="openSipCall">拨号</el-button>

    <!--
      关键接入约定：
      1) 所有绑定字段使用 sip 前缀，避免和页面 phone/visible/recordId 冲突
      2) 高频场景加 sipOpening / sipVisible 防重复点击
      3) 有加密号优先传 sip-encrypted-phone；医生端常见明文则传 sip-plain-phone
    -->
    <call-phone-dialog
      :sip-visible.sync="sipVisible"
      :sip-encrypted-phone="sipEncryptedPhone"
      :sip-plain-phone="sipPlainPhone"
      :sip-dial-type="sipDialType"
      :sip-record-id="sipRecordId"
      @sip-call-ended="onSipCallEnded"
    />
  </div>
</template>

<script>
import CallPhoneDialog from '@/components/sipCall/sipCallPhoneDialog.vue'

export default {
  components: { CallPhoneDialog },
  data() {
    return {
      sipVisible: false,
      sipEncryptedPhone: '',
      sipPlainPhone: '',
      sipDialType: 'audio',
      sipRecordId: '',
      sipOpening: false
    }
  },
  methods: {
    /**
     * 打开一键外呼
     * @param {Object} payload
     * @param {string} [payload.encryptedPhone] 加密号码
     * @param {string} [payload.plainPhone] 明文号码
     * @param {string|number} [payload.recordId] 业务 ID（订单/客户等）
     */
    async openSipCall(payload = {}) {
      // 防重复：弹窗已开或正在准备中
      if (this.sipOpening || this.sipVisible) {
        this.$message.warning('外呼进行中，请勿重复点击')
        return
      }
      const encryptedPhone = payload.encryptedPhone || ''
      const plainPhone = payload.plainPhone || ''
      const recordId = payload.recordId
      if (!encryptedPhone && !plainPhone) {
        this.$message.warning('无可用号码')
        return
      }

      this.sipOpening = true
      try {
        this.sipEncryptedPhone = encryptedPhone
        this.sipPlainPhone = plainPhone
        this.sipRecordId = recordId == null ? '' : recordId
        this.sipDialType = 'audio'
        this.sipVisible = true
      } finally {
        this.sipOpening = false
      }
    },

    onSipCallEnded(recordId) {
      // 可在此刷新通话记录列表、更新订单状态等
      console.log('[业务页] 外呼结束, recordId=', recordId)
    }
  }
}
</script>
```

### 问诊详情页当前接入方式

```vue
<call-phone-dialog
  :sip-visible.sync="sipVisible"
  :sip-plain-phone="sipPlainPhone"
  :sip-record-id="sipRecordId"
  @sip-call-ended="onSipCallEnded"
/>
```

点击「拨号」时：

1. 防重复点击
2. 优先请求 `getOrderUserPhone(orderId)` 获取号码，失败则回退页面展示号
3. 写入 `sipPlainPhone` / `sipRecordId` 后打开弹窗
4. 弹窗内选择线路后才会继续连接 IPCC 并外呼

---

## 5. 线路选择说明

- 号码解析成功后进入「选择线路」步骤
- 线路卡片展示名称、主叫号、线路类型标签（手机/固话）
- **未选择并确认前不会连接 IPCC / 注册分机 / 外呼**
- 点击「确认线路并外呼」后，使用所选 `gatewayId` 作为 `myGateway` 请求工具条参数并连接

---

## 6. 挂断与同步（5 秒 / 防重 / 同步后再关窗）

挂断后流程：

1. 本地立即结束通话 UI，进入「等待同步」状态（**禁止手动关闭弹窗**）
2. 将 `uuid` 规范化后放入同步队列（全局 + 会话去重，避免高频重复同步）
3. **延迟 5 秒**再调用 `callEndSyncByUuid`（等待后端生成话单）
4. 失败最多重试 2 次；重复/不存在类错误视为已处理
5. **无论同步成功或失败**，都进入短倒计时后**自动关闭窗口**
6. 同步完成前：遮罩点击、Esc、右上角关闭均不可提前关掉

---

## 7. 前置条件与注意事项

1. 当前登录医生需已绑定 SIP 分机（外呼配置页）
2. 首次使用需授权浏览器麦克风
3. 加密号场景需配置环境变量 `VUE_APP_SIP_PHONE_ENCRYPT_PRIVATE_KEY`
4. 明文号场景请确认业务允许（注意隐私合规）
5. 通话中请通过弹窗内「挂断」结束；强制关页会尽量兜底同步
6. 请勿在业务页再定义同名 `visible` / `phone` / `recordId` 去绑定本组件，统一用 `sip*` 字段

---

## 8. 常见问题

| 现象 | 可能原因 | 处理 |
|---|---|---|
| 提示未查询到分机号 | 未做外呼配置绑定 | 到「外呼信息 → 外呼配置」绑定分机/线路 |
| 提示暂无可用线路 | 网关列表为空 | 检查 `remoteGateWayList` / 工具条网关配置 |
| 解密失败 | 传了明文却走加密流程，或私钥缺失 | 明文用 `sip-plain-phone`；加密用 `sip-encrypted-phone` |
| 重复点击无反应/提示进行中 | 防重锁生效 | 等当前弹窗结束再拨 |
| 记录未同步 | 挂断后不足 5 秒刷新、或 uuid 为空 | 等待同步完成；检查挂断事件是否产出 uuid |

---

## 9. 版本约定

- Props / Events 以 `sip` 前缀为准（旧的 `visible` / `encryptedPhone` / `recordId` / `call-ended` 已废弃）
- 同步延迟：`5s`
- 自动关闭倒计时：`5s`
