Files
zyt/admin/src/utils/im-business-message-parse.ts
T
2026-05-07 16:42:29 +08:00

139 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string, unknown>): 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<string, string> = {
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<string, unknown>
try {
o = JSON.parse(s) as Record<string, unknown>
} 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<string, unknown>)
}
if (o.businessID === 'rtc_call' || o.cmd) {
return parseRtcInner(o as Record<string, unknown>)
}
return null
}