diff --git a/TUICallKit-Vue3/TUIKit/pages/chat/chat.vue b/TUICallKit-Vue3/TUIKit/pages/chat/chat.vue
index 4c06c085..e20499f2 100644
--- a/TUICallKit-Vue3/TUIKit/pages/chat/chat.vue
+++ b/TUICallKit-Vue3/TUIKit/pages/chat/chat.vue
@@ -499,6 +499,7 @@ const { proxy } = getCurrentInstance()
uni.showToast({ title: '播放失败', icon: 'none' });
});
}
+ let patient_data=ref();
const video =async (userid)=>{
const uid=userid.replace('patient_', '')
@@ -513,6 +514,7 @@ const { proxy } = getCurrentInstance()
const {userId } = res.data;
+ patient_data.value=res.data.data
const { userSig, SDKAppID } = GenerateTestUserSig.genTestUserSig({
userID:userId,
});
@@ -583,19 +585,45 @@ const { proxy } = getCurrentInstance()
chat.use(richMediaMessagePlugin);
chat.use(groupPlugin);
- try {
- await chat.login({ userID, userSig });
- } catch (loginErr) {
- const code = loginErr?.code ?? loginErr?.error?.code;
- const msg = String(loginErr?.message ?? loginErr?.error?.message ?? '');
- const isDuplicateLogin = code === 2025 || msg.includes('重复登录');
- if (isDuplicateLogin) {
- // 重复登录:SDK 可能已在内部处理,等待后继续;若仍不可用,后续 updateMyProfile/loadHistoryMessages 会静默失败
- await new Promise(r => setTimeout(r, 300));
- } else {
- throw loginErr;
+ const doLogin = () => chat.login({ userID, userSig });
+ const getLoginCode = (err) => {
+ const c = err?.code ?? err?.error?.code;
+ if (c != null) return c;
+ const msg = String(err?.message ?? err?.error?.message ?? '');
+ const m = msg.match(/["']?code["']?\s*:\s*(\d+)/);
+ return m ? parseInt(m[1], 10) : null;
+ };
+ let loginOk = false;
+ for (let retry = 0; retry <= 2; retry++) {
+ try {
+ await doLogin();
+ loginOk = true;
+ break;
+ } catch (loginErr) {
+ const code = getLoginCode(loginErr);
+ const msg = String(loginErr?.message ?? loginErr?.error?.message ?? '');
+ const isDuplicateLogin = code === 2025 || msg.includes('重复登录');
+ if (isDuplicateLogin && retry < 2) {
+ // 2025 重复登录:销毁当前实例,等待后重建并重试
+ try {
+ await chat.destroy?.();
+ } catch (_) {}
+ chat = null;
+ await new Promise(r => setTimeout(r, 600));
+ chat = TencentCloudChat.create({
+ SDKAppID: Number(SDKAppID),
+ scene: 'uniapp-wx'
+ });
+ chat.use(conversationPlugin);
+ chat.use(messageEnhancerPlugin);
+ chat.use(richMediaMessagePlugin);
+ chat.use(groupPlugin);
+ } else {
+ throw loginErr;
+ }
}
}
+ if (!loginOk) throw new Error('IM 登录失败');
// updateMyProfile 非必需,失败不阻塞聊天(如 2024 用户未登录时可忽略)
try {
await chat.updateMyProfile({
@@ -615,6 +643,9 @@ const { proxy } = getCurrentInstance()
await loadHistoryMessages();
+ // 打开会话时通知医生(IM自定义消息 + 后端全局事件)
+ notifyDoctorOnChatOpen();
+
if(options.msg==1){
console.log('msg我进来了',options.msg)
await sendConfirmationMessage(); // 进入聊天时自动发送知情确认
@@ -673,6 +704,47 @@ const { proxy } = getCurrentInstance()
}
});
+ /** 打开会话时通知医生:发送IM自定义消息 + 调用后端全局事件 */
+ async function notifyDoctorOnChatOpen() {
+ 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 {
+ // 1. 发送 IM 自定义消息给医生
+ const customPayload = {
+ businessID: 'patient_opened_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 {
+ // 2. 调用后端 API 触发全局事件(供 admin 轮询)
+ await proxy.apiUrl({
+ url: '/api/chat/notifyOpen',
+ method: 'POST',
+ data: {
+ doctor_id: doctorId,
+ patient_id: patientId,
+ patient_name: patientName
+ }
+ }, false);
+ } catch (e) {
+ console.warn('后端通知失败:', e);
+ }
+ }
+
async function loadHistoryMessages() {
try {
const res = await chat.getMessageList({ conversationID, count: 15 });
@@ -745,10 +817,14 @@ const { proxy } = getCurrentInstance()
try {
const customData = JSON.parse(msg.payload.data);
console.log('自定义消息:', customData);
-
+
// 过滤掉输入状态消息
if (customData.businessID === 'user_typing_status') {
console.log('过滤输入状态消息');
+ return null;
+ }
+ // 过滤掉患者打开会话通知(仅用于医生端提醒,不在聊天列表展示)
+ if (customData.businessID === 'patient_opened_chat') {
return null; // 返回null表示不显示此消息
}
diff --git a/TUICallKit-Vue3/pages/order/monad/monad.vue b/TUICallKit-Vue3/pages/order/monad/monad.vue
index 110ed1fc..64541033 100644
--- a/TUICallKit-Vue3/pages/order/monad/monad.vue
+++ b/TUICallKit-Vue3/pages/order/monad/monad.vue
@@ -824,31 +824,24 @@ const confirmDiagnosis = async (diagnosisId) => {
- uni.showLoading({ title: '提交中...' });
+
const app = getApp();
- const rawUserID = app?.globalData?.userID;
- const userID = (typeof rawUserID === 'string' ? rawUserID : '').replace(/patient_/g, '');
- if (!userID) {
- uni.hideLoading();
- uni.showToast({ title: '请先登录', icon: 'none' });
- return;
- }
+
try {
const res = await proxy.apiUrl({
url: '/api/tcm/confirmDiagnosis',
method: 'POST',
data: {
id: diagnosisId,
- user_id: userID || 0,
share_user: share_user_id || 0
}
}, false);
if (res.code === 1) {
- dingdan_ok.value=res.data.is_view?true:false
+
uni.setStorageSync('dingdan_ok', true);
- dingdan_ok.value=true;
+
} else {
uni.showToast({ title: res.msg || '确认失败', icon: 'none' });
diff --git a/admin/src/App.vue b/admin/src/App.vue
index 3747bc29..2355165c 100644
--- a/admin/src/App.vue
+++ b/admin/src/App.vue
@@ -2,13 +2,15 @@
import { useDark, useThrottleFn, useWindowSize } from '@vueuse/core'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
-
import { ScreenEnum } from './enums/appEnums'
import useAppStore from './stores/modules/app'
import useSettingStore from './stores/modules/setting'
+import useUserStore from './stores/modules/user'
+import ChatNotifyToast from './components/chat-notify-toast/index.vue'
const appStore = useAppStore()
const settingStore = useSettingStore()
+const userStore = useUserStore()
const elConfig = {
zIndex: 2000,
locale: zhCn
@@ -43,7 +45,7 @@ watch(
{const _=P,b=K,i=T,f=q,g=R,B=j,I=Q,S=G,A=M,J=H,O=L;return V(),v(Z,null,[e(_,{shadow:"never",class:"!border-none flex"},{default:t(()=>[...l[3]||(l[3]=[a("div",{class:"title flex items-center before:w-[3px] before:h-[14px] before:block before:bg-primary before:mr-2 text-xl font-medium"},[h(" 底部导航设置 "),a("span",{class:"form-tips ml-[10px] !mt-0"}," 至少添加2个导航,最多添加5个导航 ")],-1)])]),_:1}),e(O,{"label-width":"70px"},{default:t(()=>[e(_,{shadow:"never",class:"!border-none flex mt-2"},{default:t(()=>[l[4]||(l[4]=a("div",{class:"flex items-end mb-4"},[a("div",{class:"text-base text-[#101010] font-medium"},"展示样式")],-1)),e(i,{label:"默认颜色"},{default:t(()=>[e(b,{class:"max-w-[400px]",modelValue:m(n).style.default_color,"onUpdate:modelValue":l[0]||(l[0]=r=>m(n).style.default_color=r),"default-color":"#999999"},null,8,["modelValue"])]),_:1}),e(i,{label:"选中颜色",style:{"margin-bottom":"0"}},{default:t(()=>[e(b,{class:"max-w-[400px]",modelValue:m(n).style.selected_color,"onUpdate:modelValue":l[1]||(l[1]=r=>m(n).style.selected_color=r),"default-color":"#4173ff"},null,8,["modelValue"])]),_:1})]),_:1}),e(_,{shadow:"never",class:"!border-none flex mt-2"},{default:t(()=>{var r;return[l[7]||(l[7]=a("div",{class:"flex items-end mb-4"},[a("div",{class:"text-base text-[#101010] font-medium"},"菜单设置"),a("div",{class:"text-xs text-tx-secondary ml-2"},"建议图片尺寸:100px*100px")],-1)),a("div",le,[e(m(W),{class:"draggable",modelValue:m(n).list,"onUpdate:modelValue":l[2]||(l[2]=o=>m(n).list=o),animation:"300",draggable:".draggable",handle:".drag-move",move:F,"item-key":"index"},{item:t(({element:o,index:u})=>[e(A,{onClose:d=>D(u),class:ee(["max-w-[400px]",{draggable:u!=0}]),"show-close":u!==0},{default:t(()=>[a("div",oe,[e(i,{label:"导航图标"},{default:t(()=>[e(g,{modelValue:o.unselected,"onUpdate:modelValue":d=>o.unselected=d,"upload-class":"bg-body","exclude-domain":"",size:"60px"},{upload:t(()=>[a("div",te,[e(f,{name:"el-icon-Plus",size:16}),l[5]||(l[5]=a("span",{class:"text-xs leading-5"}," 未选中 ",-1))])]),_:1},8,["modelValue","onUpdate:modelValue"]),e(g,{modelValue:o.selected,"onUpdate:modelValue":d=>o.selected=d,"exclude-domain":"","upload-class":"bg-body",size:"60px"},{upload:t(()=>[a("div",ae,[e(f,{name:"el-icon-Plus",size:16}),l[6]||(l[6]=a("span",{class:"text-xs leading-5"}," 选中 ",-1))])]),_:1},8,["modelValue","onUpdate:modelValue"])]),_:2},1024),e(i,{label:"导航名称"},{default:t(()=>[e(B,{modelValue:o.name,"onUpdate:modelValue":d=>o.name=d,placeholder:"请输入名称"},null,8,["modelValue","onUpdate:modelValue"])]),_:2},1024),e(i,{label:"链接地址"},{default:t(()=>[e(I,{"is-tab":!0,disabled:u===0,modelValue:o.link,"onUpdate:modelValue":d=>o.link=d},null,8,["disabled","modelValue","onUpdate:modelValue"])]),_:2},1024),e(i,{label:"是否显示"},{default:t(()=>[a("div",se,[e(S,{disabled:u==0,modelValue:o.is_show,"onUpdate:modelValue":d=>o.is_show=d,"active-value":1,"inactive-value":0,onChange:d=>N(o)},null,8,["disabled","modelValue","onUpdate:modelValue","onChange"]),a("div",ne,[e(f,{name:"el-icon-Rank",size:"18"})])])]),_:2},1024)])]),_:2},1032,["onClose","show-close","class"])]),_:1},8,["modelValue"])]),((r=m(n).list)==null?void 0:r.length) {const _=P,b=K,i=T,f=q,g=R,B=j,I=Q,S=G,A=M,J=H,O=L;return V(),v(Z,null,[e(_,{shadow:"never",class:"!border-none flex"},{default:t(()=>[...l[3]||(l[3]=[a("div",{class:"title flex items-center before:w-[3px] before:h-[14px] before:block before:bg-primary before:mr-2 text-xl font-medium"},[h(" 底部导航设置 "),a("span",{class:"form-tips ml-[10px] !mt-0"}," 至少添加2个导航,最多添加5个导航 ")],-1)])]),_:1}),e(O,{"label-width":"70px"},{default:t(()=>[e(_,{shadow:"never",class:"!border-none flex mt-2"},{default:t(()=>[l[4]||(l[4]=a("div",{class:"flex items-end mb-4"},[a("div",{class:"text-base text-[#101010] font-medium"},"展示样式")],-1)),e(i,{label:"默认颜色"},{default:t(()=>[e(b,{class:"max-w-[400px]",modelValue:m(n).style.default_color,"onUpdate:modelValue":l[0]||(l[0]=r=>m(n).style.default_color=r),"default-color":"#999999"},null,8,["modelValue"])]),_:1}),e(i,{label:"选中颜色",style:{"margin-bottom":"0"}},{default:t(()=>[e(b,{class:"max-w-[400px]",modelValue:m(n).style.selected_color,"onUpdate:modelValue":l[1]||(l[1]=r=>m(n).style.selected_color=r),"default-color":"#4173ff"},null,8,["modelValue"])]),_:1})]),_:1}),e(_,{shadow:"never",class:"!border-none flex mt-2"},{default:t(()=>{var r;return[l[7]||(l[7]=a("div",{class:"flex items-end mb-4"},[a("div",{class:"text-base text-[#101010] font-medium"},"菜单设置"),a("div",{class:"text-xs text-tx-secondary ml-2"},"建议图片尺寸:100px*100px")],-1)),a("div",le,[e(m(W),{class:"draggable",modelValue:m(n).list,"onUpdate:modelValue":l[2]||(l[2]=o=>m(n).list=o),animation:"300",draggable:".draggable",handle:".drag-move",move:F,"item-key":"index"},{item:t(({element:o,index:u})=>[e(A,{onClose:d=>D(u),class:ee(["max-w-[400px]",{draggable:u!=0}]),"show-close":u!==0},{default:t(()=>[a("div",oe,[e(i,{label:"导航图标"},{default:t(()=>[e(g,{modelValue:o.unselected,"onUpdate:modelValue":d=>o.unselected=d,"upload-class":"bg-body","exclude-domain":"",size:"60px"},{upload:t(()=>[a("div",te,[e(f,{name:"el-icon-Plus",size:16}),l[5]||(l[5]=a("span",{class:"text-xs leading-5"}," 未选中 ",-1))])]),_:1},8,["modelValue","onUpdate:modelValue"]),e(g,{modelValue:o.selected,"onUpdate:modelValue":d=>o.selected=d,"exclude-domain":"","upload-class":"bg-body",size:"60px"},{upload:t(()=>[a("div",ae,[e(f,{name:"el-icon-Plus",size:16}),l[6]||(l[6]=a("span",{class:"text-xs leading-5"}," 选中 ",-1))])]),_:1},8,["modelValue","onUpdate:modelValue"])]),_:2},1024),e(i,{label:"导航名称"},{default:t(()=>[e(B,{modelValue:o.name,"onUpdate:modelValue":d=>o.name=d,placeholder:"请输入名称"},null,8,["modelValue","onUpdate:modelValue"])]),_:2},1024),e(i,{label:"链接地址"},{default:t(()=>[e(I,{"is-tab":!0,disabled:u===0,modelValue:o.link,"onUpdate:modelValue":d=>o.link=d},null,8,["disabled","modelValue","onUpdate:modelValue"])]),_:2},1024),e(i,{label:"是否显示"},{default:t(()=>[a("div",se,[e(S,{disabled:u==0,modelValue:o.is_show,"onUpdate:modelValue":d=>o.is_show=d,"active-value":1,"inactive-value":0,onChange:d=>N(o)},null,8,["disabled","modelValue","onUpdate:modelValue","onChange"]),a("div",ne,[e(f,{name:"el-icon-Rank",size:"18"})])])]),_:2},1024)])]),_:2},1032,["onClose","show-close","class"])]),_:1},8,["modelValue"])]),((r=m(n).list)==null?void 0:r.length)