From c033b21b029d02a81833e903bff3632fa1ce32b0 Mon Sep 17 00:00:00 2001 From: duoaohui <928970622@qq.com> Date: Sun, 7 Jun 2026 11:37:19 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=86=E9=A2=91=E9=80=9A=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/cloud-law/video-call.vue | 3 +- frontend/src/utils/cloudLawVoiceCall.js | 51 +++++++++++++++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/cloud-law/video-call.vue b/frontend/src/pages/cloud-law/video-call.vue index c0e745a..fa33bd9 100644 --- a/frontend/src/pages/cloud-law/video-call.vue +++ b/frontend/src/pages/cloud-law/video-call.vue @@ -481,7 +481,8 @@ const requestCloudLawCommands = (url) => { const notifyCloudLawEnd = () => { if (!callId.value) return Promise.resolve() const role = isLawRole.value ? 'law' : 'caller' - return notifyCloudLawBackendEnd(callId.value, role, cloudLawApiBase.value, cloudLawToken.value) + const roomCode = meetingStore.currentRoom?.room_code || formattedCode.value || '' + return notifyCloudLawBackendEnd(callId.value, role, cloudLawApiBase.value, cloudLawToken.value, roomCode) } const buildMiniProgramReturnUrl = () => { diff --git a/frontend/src/utils/cloudLawVoiceCall.js b/frontend/src/utils/cloudLawVoiceCall.js index eee7b14..2684ebe 100644 --- a/frontend/src/utils/cloudLawVoiceCall.js +++ b/frontend/src/utils/cloudLawVoiceCall.js @@ -16,24 +16,55 @@ export function resolveCloudLawApiUrl(path, apiBase) { } /** - * 通知云律后端结束通话(律师端 /api/law/voiceCall,用户端 /api/applet/voiceConnection) + * 通知云律后端结束通话。 + * - 律师端:/api/law/voiceCall/{callId}/end(PC 同域,带登录态) + * - 用户端:/api/applet/voiceConnection/publicEnd/{callId}?roomCode=xxx + * web-view 跨域调带登录态的 /{callId}/end 会被浏览器 CORS 拦截、根本到不了后端, + * 导致「挂断后 status 一直 accepted、律师端不结束、用户端不跳转」。 + * publicEnd 是 @CrossOrigin 公开端点,以 callId+roomCode 配对为凭证,可靠收尾。 */ -export function notifyCloudLawBackendEnd(callId, role = 'law', apiBase = '', token = '') { +export function notifyCloudLawBackendEnd(callId, role = 'law', apiBase = '', token = '', roomCode = '') { if (!callId) return Promise.resolve(false) - const endBase = String(role).toLowerCase() === 'law' ? '/api/law/voiceCall' : '/api/applet/voiceConnection' - const url = resolveCloudLawApiUrl(`${endBase}/${encodeURIComponent(callId)}/end`, apiBase) - const header = { 'Content-Type': 'application/json' } - if (token) { - header.token = token.startsWith('Bearer') ? token : `Bearer ${token}` + const isLaw = String(role).toLowerCase() === 'law' + if (isLaw) { + const url = resolveCloudLawApiUrl(`/api/law/voiceCall/${encodeURIComponent(callId)}/end`, apiBase) + const header = { 'Content-Type': 'application/json' } + if (token) { + header.token = token.startsWith('Bearer') ? token : `Bearer ${token}` + } + return new Promise(resolve => { + uni.request({ url, method: 'POST', data: {}, header, timeout: 8000, complete: () => resolve(true) }) + }) } + + // 用户端:publicEnd(@CrossOrigin 公开端点)。凭证是 URL 里的 callId+roomCode,无需 token/body。 + // 关键:不能带 application/json 或自定义头,否则触发 CORS 预检;预检失败时浏览器会直接 + // 掐掉真正的 POST,表现为「按钮触发了但没有请求发到后端」。 + // 因此优先用 navigator.sendBeacon(简单 POST、无预检、跨域也能到达服务器)。 + const query = roomCode ? `?roomCode=${encodeURIComponent(roomCode)}` : '' + const url = resolveCloudLawApiUrl(`/api/applet/voiceConnection/publicEnd/${encodeURIComponent(callId)}${query}`, apiBase) + console.log('[cloudLawVoiceCall] publicEnd ->', url) + // #ifdef H5 + try { + if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') { + const ok = navigator.sendBeacon(url) + console.log('[cloudLawVoiceCall] sendBeacon publicEnd result:', ok) + if (ok) return Promise.resolve(true) + } + } catch (e) { + console.warn('[cloudLawVoiceCall] sendBeacon failed', e) + } + // #endif + // 兜底:uni.request 不带任何自定义头 / body,尽量保持「简单请求」避免预检。 return new Promise(resolve => { uni.request({ url, method: 'POST', - data: {}, - header, timeout: 8000, - complete: () => resolve(true) + complete: (res) => { + console.log('[cloudLawVoiceCall] publicEnd request done:', res && res.statusCode) + resolve(true) + } }) }) }