更新
This commit is contained in:
@@ -164,7 +164,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch, computed } from 'vue';
|
import { ref, watch, computed } from 'vue';
|
||||||
import { onHide, onUnload } from '@dcloudio/uni-app'
|
import { onUnload } from '@dcloudio/uni-app'
|
||||||
import Avatar from '../Avatar/Avatar.vue';
|
import Avatar from '../Avatar/Avatar.vue';
|
||||||
import TRTCPusher from '@tencentcloud/trtc-component-uniapp/src/components/TRTCPusher.vue';
|
import TRTCPusher from '@tencentcloud/trtc-component-uniapp/src/components/TRTCPusher.vue';
|
||||||
import TRTCPlayer from '@tencentcloud/trtc-component-uniapp/src/components/TRTCPlayer.vue';
|
import TRTCPlayer from '@tencentcloud/trtc-component-uniapp/src/components/TRTCPlayer.vue';
|
||||||
@@ -337,6 +337,8 @@ onUnload(() => {
|
|||||||
const callRole = callParticipantInfo?.value?.selfInfo?.role;
|
const callRole = callParticipantInfo?.value?.selfInfo?.role;
|
||||||
|
|
||||||
if (callStatus === CallStatus.IDLE) return;
|
if (callStatus === CallStatus.IDLE) return;
|
||||||
|
// 已接通:页面卸载(返回、切后台、关闭小程序等)不主动 signaling 挂断,医生端可继续面诊;结束请点「挂断」
|
||||||
|
if (callStatus === CallStatus.CONNECTED) return;
|
||||||
if (callStatus === CallStatus.CALLING) {
|
if (callStatus === CallStatus.CALLING) {
|
||||||
if (callRole === CallRole.CALLER) {
|
if (callRole === CallRole.CALLER) {
|
||||||
hangup();
|
hangup();
|
||||||
@@ -344,9 +346,6 @@ onUnload(() => {
|
|||||||
reject();
|
reject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (callStatus === CallStatus.CONNECTED) {
|
|
||||||
hangup();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -277,6 +277,8 @@ const { proxy } = getCurrentInstance()
|
|||||||
let unsubscribeWxAppShow = null;
|
let unsubscribeWxAppShow = null;
|
||||||
let allowChatForegroundRelogin = false;
|
let allowChatForegroundRelogin = false;
|
||||||
let foregroundReloginTimer = null;
|
let foregroundReloginTimer = null;
|
||||||
|
/** 避免 onUnload 重复上报离开 */
|
||||||
|
let patientLeaveNotified = false;
|
||||||
|
|
||||||
const CONFIRMATION_MESSAGE = `我已确认以下信息:
|
const CONFIRMATION_MESSAGE = `我已确认以下信息:
|
||||||
1、本人确认已在线下确诊;
|
1、本人确认已在线下确诊;
|
||||||
@@ -611,6 +613,7 @@ const { proxy } = getCurrentInstance()
|
|||||||
}
|
}
|
||||||
onLoad(async (options) => {
|
onLoad(async (options) => {
|
||||||
try {
|
try {
|
||||||
|
patientLeaveNotified = false;
|
||||||
console.log('进入onMounted:', options);
|
console.log('进入onMounted:', options);
|
||||||
const app = getApp();
|
const app = getApp();
|
||||||
const userID = options.userID|| app.globalData.userID;
|
const userID = options.userID|| app.globalData.userID;
|
||||||
@@ -790,6 +793,11 @@ const { proxy } = getCurrentInstance()
|
|||||||
});
|
});
|
||||||
|
|
||||||
onUnload(() => {
|
onUnload(() => {
|
||||||
|
// 患者退出聊天页:通知后端(与 notifyOpen 对称,供管理端轮询)
|
||||||
|
if (!patientLeaveNotified && targetUserID && String(targetUserID).startsWith('doctor_')) {
|
||||||
|
patientLeaveNotified = true;
|
||||||
|
void notifyDoctorOnChatClose();
|
||||||
|
}
|
||||||
allowChatForegroundRelogin = false;
|
allowChatForegroundRelogin = false;
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
if (typeof unsubscribeWxAppShow === 'function') {
|
if (typeof unsubscribeWxAppShow === 'function') {
|
||||||
@@ -856,6 +864,47 @@ const { proxy } = getCurrentInstance()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 离开会话页时通知医生:IM 自定义消息 + 后端全局事件 */
|
||||||
|
async function notifyDoctorOnChatClose() {
|
||||||
|
if (!targetUserID || !targetUserID.startsWith('doctor_')) return;
|
||||||
|
const doctorId = targetUserID.replace('doctor_', '');
|
||||||
|
const userData = uni.getStorageSync('userData') || {};
|
||||||
|
const patientName = userData.name || patient_data.value?.patient_name || '患者';
|
||||||
|
const patientId = (currentUserID || '').replace('patient_', '') || '';
|
||||||
|
try {
|
||||||
|
if (chat) {
|
||||||
|
const customPayload = {
|
||||||
|
businessID: 'patient_closed_chat',
|
||||||
|
patientId,
|
||||||
|
patientName,
|
||||||
|
doctorId,
|
||||||
|
time: Date.now()
|
||||||
|
};
|
||||||
|
const imMsg = chat.createCustomMessage({
|
||||||
|
to: targetUserID,
|
||||||
|
conversationType: TencentCloudChat.TYPES.CONV_C2C,
|
||||||
|
payload: { data: JSON.stringify(customPayload) }
|
||||||
|
});
|
||||||
|
await chat.sendMessage(imMsg);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('IM 离开通知发送失败:', e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await proxy.apiUrl({
|
||||||
|
url: '/api/chat/notifyClose',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
doctor_id: doctorId,
|
||||||
|
patient_id: patientId,
|
||||||
|
patient_name: patientName
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('后端离开通知失败:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function applyDoctorEnteredSignalFromRawMessage(msg) {
|
function applyDoctorEnteredSignalFromRawMessage(msg) {
|
||||||
if (!msg || msg.type !== TencentCloudChat.TYPES.MSG_CUSTOM) return;
|
if (!msg || msg.type !== TencentCloudChat.TYPES.MSG_CUSTOM) return;
|
||||||
try {
|
try {
|
||||||
@@ -956,6 +1005,9 @@ const { proxy } = getCurrentInstance()
|
|||||||
if (customData.businessID === 'patient_opened_chat') {
|
if (customData.businessID === 'patient_opened_chat') {
|
||||||
return null; // 返回null表示不显示此消息
|
return null; // 返回null表示不显示此消息
|
||||||
}
|
}
|
||||||
|
if (customData.businessID === 'patient_closed_chat') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
// 医生进入诊室信令(admin 发送):仅更新顶部卡片,不在聊天列表展示
|
// 医生进入诊室信令(admin 发送):仅更新顶部卡片,不在聊天列表展示
|
||||||
if (customData.businessID === 'doctor_entered_consult_room') {
|
if (customData.businessID === 'doctor_entered_consult_room') {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -9,17 +9,35 @@
|
|||||||
v-for="item in notifications"
|
v-for="item in notifications"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="chat-notify-card"
|
class="chat-notify-card"
|
||||||
:class="{ 'chat-notify-card-complete': item.type === 'consultation_complete' }"
|
:class="{
|
||||||
|
'chat-notify-card-complete': item.type === 'consultation_complete',
|
||||||
|
'chat-notify-card-left': item.type === 'patient_left_chat'
|
||||||
|
}"
|
||||||
@click="handleClick(item)"
|
@click="handleClick(item)"
|
||||||
>
|
>
|
||||||
<div class="chat-notify-icon" :class="{ 'chat-notify-icon-complete': item.type === 'consultation_complete' }">
|
<div
|
||||||
|
class="chat-notify-icon"
|
||||||
|
:class="{
|
||||||
|
'chat-notify-icon-complete': item.type === 'consultation_complete',
|
||||||
|
'chat-notify-icon-left': item.type === 'patient_left_chat'
|
||||||
|
}"
|
||||||
|
>
|
||||||
<el-icon :size="22">
|
<el-icon :size="22">
|
||||||
<ChatDotRound v-if="item.type !== 'consultation_complete'" />
|
<ChatDotRound v-if="item.type !== 'consultation_complete' && item.type !== 'patient_left_chat'" />
|
||||||
<CircleCheck v-else />
|
<CircleCheck v-else-if="item.type === 'consultation_complete'" />
|
||||||
|
<Right v-else />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-notify-body">
|
<div class="chat-notify-body">
|
||||||
<div class="chat-notify-title">{{ item.type === 'consultation_complete' ? '面诊结束' : '患者打开会话' }}</div>
|
<div class="chat-notify-title">
|
||||||
|
{{
|
||||||
|
item.type === 'consultation_complete'
|
||||||
|
? '面诊结束'
|
||||||
|
: item.type === 'patient_left_chat'
|
||||||
|
? '患者离开会话'
|
||||||
|
: '患者打开会话'
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
<div class="chat-notify-desc">
|
<div class="chat-notify-desc">
|
||||||
<template v-if="item.type === 'consultation_complete'">
|
<template v-if="item.type === 'consultation_complete'">
|
||||||
<span class="patient-name">{{ item.patient_name || '患者' }}</span>
|
<span class="patient-name">{{ item.patient_name || '患者' }}</span>
|
||||||
@@ -27,6 +45,10 @@
|
|||||||
<span class="doctor-name">{{ item.doctor_name || '医生' }}</span>
|
<span class="doctor-name">{{ item.doctor_name || '医生' }}</span>
|
||||||
完成,请及时跟进
|
完成,请及时跟进
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="item.type === 'patient_left_chat'">
|
||||||
|
<span class="patient-name">{{ item.patient_name || '患者' }}</span>
|
||||||
|
已离开问诊会话页面
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span class="patient-name">{{ item.patient_name || '患者' }}</span>
|
<span class="patient-name">{{ item.patient_name || '患者' }}</span>
|
||||||
已打开与您的会话,请及时查看
|
已打开与您的会话,请及时查看
|
||||||
@@ -45,14 +67,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ChatDotRound, CircleCheck, Close } from '@element-plus/icons-vue'
|
import { ChatDotRound, CircleCheck, Close, Right } from '@element-plus/icons-vue'
|
||||||
import { getChatNotifications } from '@/api/chat'
|
import { getChatNotifications } from '@/api/chat'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
export interface ChatNotifyItem {
|
export interface ChatNotifyItem {
|
||||||
id: string
|
id: string
|
||||||
type?: 'patient_opened_chat' | 'consultation_complete'
|
type?: 'patient_opened_chat' | 'patient_left_chat' | 'consultation_complete'
|
||||||
doctor_id?: number
|
doctor_id?: number
|
||||||
assistant_id?: number
|
assistant_id?: number
|
||||||
patient_id?: string
|
patient_id?: string
|
||||||
@@ -173,6 +195,15 @@ html.dark .chat-notify-card:hover {
|
|||||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-notify-card-left {
|
||||||
|
border-color: rgba(148, 163, 184, 0.35);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(148, 163, 184, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-notify-icon-left {
|
||||||
|
background: linear-gradient(135deg, #94a3b8 0%, #64748b 100%);
|
||||||
|
}
|
||||||
|
|
||||||
.chat-notify-body {
|
.chat-notify-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|||||||
@@ -99,6 +99,15 @@ export function parseImBusinessPayload(raw: string): FriendlyParse | null {
|
|||||||
return { main: '患者进入聊天', sub: parts.length ? parts.join(' · ') : undefined, tag: '诊室' }
|
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') {
|
if (o.businessID === 'user_typing_status') {
|
||||||
return { main: '对方正在输入…', tag: '状态' }
|
return { main: '对方正在输入…', tag: '状态' }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use think\facade\Db;
|
|||||||
*/
|
*/
|
||||||
class ChatController extends BaseApiController
|
class ChatController extends BaseApiController
|
||||||
{
|
{
|
||||||
public array $notNeedLogin = ['notifyOpen', 'logAvPermission'];
|
public array $notNeedLogin = ['notifyOpen', 'notifyClose', 'logAvPermission'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上报音视频权限拒绝日志
|
* 上报音视频权限拒绝日志
|
||||||
@@ -55,4 +55,21 @@ class ChatController extends BaseApiController
|
|||||||
ChatNotifyLogic::addNotify($doctorId, $patientId ?: '0', $patientName);
|
ChatNotifyLogic::addNotify($doctorId, $patientId ?: '0', $patientName);
|
||||||
return $this->success('已通知');
|
return $this->success('已通知');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 患者离开会话页时通知医生(管理后台轮询 + 可与 IM 信令配合)
|
||||||
|
*/
|
||||||
|
public function notifyClose()
|
||||||
|
{
|
||||||
|
$doctorId = (int) ($this->request->post('doctor_id') ?: $this->request->post('doctorId'));
|
||||||
|
$patientId = trim((string) ($this->request->post('patient_id') ?: $this->request->post('patientId') ?: ''));
|
||||||
|
$patientName = trim((string) ($this->request->post('patient_name') ?: $this->request->post('patientName') ?: '患者'));
|
||||||
|
|
||||||
|
if ($doctorId <= 0) {
|
||||||
|
return $this->fail('医生ID无效');
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatNotifyLogic::addPatientLeftNotify($doctorId, $patientId ?: '0', $patientName);
|
||||||
|
return $this->success('已通知');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,30 @@ class ChatNotifyLogic extends BaseLogic
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 患者离开会话页/诊室时通知医生端(管理后台轮询)
|
||||||
|
*/
|
||||||
|
public static function addPatientLeftNotify(int $doctorId, string $patientId, string $patientName): bool
|
||||||
|
{
|
||||||
|
$key = self::CACHE_PREFIX . $doctorId;
|
||||||
|
$item = [
|
||||||
|
'id' => uniqid('', true),
|
||||||
|
'type' => 'patient_left_chat',
|
||||||
|
'doctor_id' => $doctorId,
|
||||||
|
'patient_id' => $patientId,
|
||||||
|
'patient_name' => $patientName,
|
||||||
|
'created_at' => time(),
|
||||||
|
];
|
||||||
|
$list = Cache::get($key) ?: [];
|
||||||
|
if (!is_array($list)) {
|
||||||
|
$list = [];
|
||||||
|
}
|
||||||
|
array_unshift($list, $item);
|
||||||
|
$list = array_slice($list, 0, self::MAX_PER_DOCTOR);
|
||||||
|
Cache::set($key, $list, self::CACHE_TTL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加面诊结束通知(医生完成接诊后通知对应医助)
|
* 添加面诊结束通知(医生完成接诊后通知对应医助)
|
||||||
* @param int $assistantId 医助ID (admin_id)
|
* @param int $assistantId 医助ID (admin_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user