import dayjs from 'dayjs' export type FriendlyParse = { main: string; sub?: string; tag?: string } /** 业务时间:支持秒级时间戳或毫秒(字符串数字) */ export function formatBizTime(t: unknown): string | undefined { if (t == null || t === '') return undefined const n = typeof t === 'string' && /^\d+$/.test(t.trim()) ? parseInt(t.trim(), 10) : Number(t) if (!Number.isFinite(n) || n <= 0) return undefined return n > 1e12 ? dayjs(n).format('YYYY-MM-DD HH:mm:ss') : dayjs.unix(n).format('YYYY-MM-DD HH:mm:ss') } export function parseRtcInner(inner: Record): FriendlyParse { const callType = inner.call_type ?? inner.callType ?? 2 const callTypeLabel = callType === 2 ? '视频' : callType === 1 ? '语音通话' : '通话' let cmd = String(inner.cmd || inner.action || '').toLowerCase() if (!cmd) { const at = Number(inner.actionType ?? inner.action_type ?? inner.call_action) if (at === 1) cmd = 'invite' else if (at === 2) cmd = 'cancel' else if (at === 3) cmd = 'accept' else if (at === 4) cmd = 'reject' else if (at === 5) cmd = 'timeout' else if (at === 6) cmd = 'hangup' else if (at === 7) cmd = 'linebusy' } const cmdMap: Record = { hangup: '已结束', invite: '发起通话', linebusy: '对方忙线', cancel: '已取消呼叫', reject: '已拒绝接听', accept: '已接听', timeout: '无人接听' } const action = cmdMap[cmd] || (cmd ? `状态:${cmd}` : '') // 如果有通话时长,追加显示 const duration = Number(inner.duration ?? inner.call_duration) const durationText = (duration > 0 && (action === '已结束' || action === '已挂断')) ? ` (${Math.floor(duration / 60)}分${duration % 60}秒)` : '' const main = action ? `${callTypeLabel} · ${action}${durationText}` : callTypeLabel const parts: string[] = [] //if (inner.inviter) parts.push(`发起方: ${inner.inviter}`) if (inner.invitee) parts.push(`对方: ${inner.invitee}`) if (inner.groupID) parts.push(`群组: ${inner.groupID}`) // 强制输出兜底调试信息(仅未识别到 action 时触发) if (!action) { const dbg = JSON.stringify(inner).replace(/"/g, '') parts.push(`(原始参数: ${dbg})`) } return { main, sub: parts.length ? parts.join(',') : undefined, tag: '通话' } } /** * 将 IM 自定义 / 信令 JSON 解析为可读文案(医生进诊室、音视频通话等)。 * 与后台 IM 漫游、小程序 TUIKit 约定字段一致。 */ export function parseImBusinessPayload(raw: string): FriendlyParse | null { const s = raw.trim() if (!s.startsWith('{')) return null let o: Record try { o = JSON.parse(s) as Record } catch { return null } if (!('businessID' in o) && !('cmd' in o)) { return null } if (o.businessID === 'doctor_entered_consult_room') { const t = formatBizTime(o.time) return { main: '医生已进入诊室', sub: t, tag: '诊室' } } if (o.businessID === 'patient_opened_chat') { const parts: string[] = [] if (o.patientName) parts.push(`患者:${String(o.patientName)}`) if (o.patientId != null && o.patientId !== '') parts.push(`患者 ID:${String(o.patientId)}`) if (o.doctorId != null && o.doctorId !== '') parts.push(`关联医生:${String(o.doctorId)}`) const t = formatBizTime(o.time) if (t) parts.push(t) return { main: '患者进入聊天', sub: parts.length ? parts.join(' · ') : undefined, tag: '诊室' } } if (o.businessID === 'patient_closed_chat') { const parts: string[] = [] if (o.patientName) parts.push(`患者:${String(o.patientName)}`) if (o.patientId != null && o.patientId !== '') parts.push(`患者 ID:${String(o.patientId)}`) const t = formatBizTime(o.time) if (t) parts.push(t) return { main: '患者离开聊天页', sub: parts.length ? parts.join(' · ') : undefined, tag: '诊室' } } if (o.businessID === 'user_typing_status') { return { main: '对方正在输入…', tag: '状态' } } if (o.businessID === 'consultation_complete') { return { main: '问诊已完成', sub: formatBizTime(o.time), tag: '诊室' } } if (o.businessID === 1 || o.businessID === '1' || o.businessID === 'av_call') { let inner: unknown = o.data if (typeof inner === 'string') { try { inner = JSON.parse(inner) } catch { // fallback } } // 深远兼容:合并外层字段和 data 层字段,避免各端版本取参有遗漏 const merged = { ...o, ...(typeof inner === 'object' && inner ? inner : {}) } return parseRtcInner(merged as Record) } if (o.businessID === 'rtc_call' || o.cmd) { return parseRtcInner(o as Record) } return null }