no message

This commit is contained in:
wangdongbo 2026-01-22 15:13:26 +08:00
parent 9b545b442d
commit 74878a131b
16 changed files with 1421 additions and 1267 deletions

View File

@ -2,3 +2,4 @@ MP_API_BASE_URL=http://localhost:8080
MP_CACHE_PREFIX=development
MP_WX_APP_ID=wx93af55767423938e
MP_CORP_ID=wwe3fb2faa52cf9dfb
MP_TIM_SDK_APP_ID=1600123876

View File

@ -1,3 +1,4 @@
MP_API_BASE_URL=http://192.168.60.2:8080
MP_CACHE_PREFIX=development
MP_WX_APP_ID=wx93af55767423938e
MP_TIM_SDK_APP_ID=1600072268

12
App.vue
View File

@ -1,5 +1,7 @@
<script>
import useAccountStore from "@/store/account.js";
import { globalTimChatManager } from "@/utils/tim-chat.js";
export default {
onLaunch: function () {
// pinia store getActivePinia
@ -12,6 +14,16 @@ export default {
},
onHide: function () {
console.log("App Hide");
// 退退IM
try {
if (globalTimChatManager && globalTimChatManager.tim) {
console.log('小程序退出开始退出腾讯IM');
globalTimChatManager.destroy();
console.log('腾讯IM退出成功');
}
} catch (error) {
console.error('退出腾讯IM失败:', error);
}
},
};
</script>

View File

@ -1,20 +1,21 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

View File

@ -6,6 +6,13 @@
"navigationBarTitleText": "消息"
}
},
{
"path": "pages/message/index",
"style": {
"navigationBarTitleText": "聊天",
"enablePullDownRefresh": false
}
},
{
"path": "pages/case/case",
"style": {

View File

@ -1,4 +1,9 @@
// SCSS 变量定义
$font-size-text: 28rpx;
$font-size-tip: 24rpx;
$font-size-title: 32rpx;
$text-color-sub: #999;
$primary-color: #0877F1;
.chat-page {
position: fixed;
@ -1231,5 +1236,4 @@
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10rpx); }
}
}

View File

@ -293,47 +293,8 @@ const cancelRecord = () => {
stopRecordUtil(recorderManager);
};
//
const sendSymptomMessage = async () => {
const symptomMessage = createCustomMessage("symptom", {
content: "发送了病情描述",
symptomContent: "患者主诉头痛、发热、咳嗽3天。既往史无特殊。现病史3天前开始出现头痛伴有发热体温最高38.5℃,同时有干咳症状。",
hasVisitedHospital: "yes",
selectedDiseases: [{ text: "感冒" }, { text: "上呼吸道感染" }],
images: ["/static/home/photo.png"],
}, props.formatTime);
await sendCustomMessage(symptomMessage);
};
//
const sendPrescriptionMessage = async () => {
const prescriptionMessage = createCustomMessage("prescription", {
content: "医生开了处方",
diagnosis: "上呼吸道感染",
medicines: [
{ name: "阿莫西林胶囊", spec: "0.25g×20粒", count: 2 },
{ name: "甲硝唑片", spec: "0.5mg×20片", count: 2 },
],
}, props.formatTime);
await sendCustomMessage(prescriptionMessage);
};
//
const sendRefillMessage = async () => {
const refillMessage = createCustomMessage("refill", {
content: "发送了续方申请",
patientName: props.patientInfo.name,
gender: props.patientInfo.gender,
age: props.patientInfo.age,
diagnosis: "慢性胃炎,脾胃虚弱",
prescriptionType: "中药处方",
prescriptionDesc: "共14剂每日1剂1剂分2次服用饭后温服",
}, props.formatTime);
await sendCustomMessage(refillMessage);
};
//
const sendSurveyMessage = async () => {

View File

@ -12,7 +12,6 @@
<text class="waiting-title">等待医生接诊.....</text>
</view>
<view class="doctor-avatar-outer">
<!-- chatInfo.avatar || -->
<image class="doctor-avatar" :src="avatar" mode="aspectFill">
</image>
</view>

View File

@ -3,7 +3,6 @@
<text v-if="message.type === 'TIMTextElem'" class="message-text">
{{ message.payload.text }}
</text>
<!-- 图片消息 -->
<image
v-else-if="message.type === 'TIMImageElem'"
@ -62,8 +61,8 @@
</view>
</view>
<!-- 自定义消息卡片 -->
<template v-else-if="message.type === 'TIMCustomElem'">
<!-- <template v-else-if="message.type === 'TIMCustomElem'">
<view
class="card-avatar-row"
@click="() => console.log('点击头像', message)"
@ -75,13 +74,13 @@
@viewDetail="$emit('viewDetail', $event)"
/>
</view>
</template>
</template> -->
</template>
<script setup>
import { computed } from "vue";
import { getParsedCustomMessage } from "@/utils/chat-utils.js";
import MessageCard from "./message-card/message-card.vue";
// import MessageCard from "./message-card/message-card.vue";
const props = defineProps({
message: Object,

View File

@ -174,12 +174,6 @@ const submitEvaluation = async () => {
emit('popupStatusChange', false);
}
loading.value = false
// //
// emit('evaluationSubmitted', {
// rating: evaluationRating.value,
// comment: evaluationComment.value
// });
};
</script>

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,501 @@
<template>
<div>message</div>
<view class="message-page">
<!-- 消息列表 -->
<scroll-view
class="message-list"
scroll-y="true"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="handleRefresh"
@scrolltolower="handleLoadMore"
>
<!-- 加载状态 -->
<view
v-if="loading && conversationList.length === 0"
class="loading-container"
>
<text class="loading-text">加载中...</text>
</view>
<!-- 消息列表项 -->
<view
v-for="conversation in conversationList"
:key="conversation.conversationID"
class="message-item"
@click="handleClickConversation(conversation)"
>
<view class="avatar-container">
<image
class="avatar"
:src="conversation.avatar || '/static/default-avatar.png'"
mode="aspectFill"
/>
<view v-if="conversation.unreadCount > 0" class="unread-badge">
<text class="unread-text">{{
conversation.unreadCount > 99 ? "99+" : conversation.unreadCount
}}</text>
</view>
</view>
<view class="content">
<view class="header">
<text class="name">{{ conversation.name || "未知群聊" }}</text>
<text class="time">{{
formatMessageTime(conversation.lastMessageTime)
}}</text>
</view>
<view class="message-preview">
<text class="preview-text">{{
conversation.lastMessage || "暂无消息"
}}</text>
</view>
</view>
</view>
<!-- 空状态 -->
<view
v-if="!loading && conversationList.length === 0"
class="empty-container"
>
<image class="empty-image" src="/static/empty.svg" mode="aspectFit" />
<text class="empty-text">暂无消息</text>
</view>
<!-- 加载更多 -->
<view v-if="hasMore && conversationList.length > 0" class="load-more">
<text class="load-more-text">{{
loadingMore ? "加载中..." : "上拉加载更多"
}}</text>
</view>
</scroll-view>
</view>
</template>
<script>
<script setup>
import { ref } from "vue";
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
import { storeToRefs } from "pinia";
import useAccountStore from "@/store/account.js";
import { globalTimChatManager } from "@/utils/tim-chat.js";
//
const { account, openid, isIMInitialized } = storeToRefs(useAccountStore());
const { initIMAfterLogin } = useAccountStore();
//
const conversationList = ref([]);
const loading = ref(false);
const loadingMore = ref(false);
const hasMore = ref(false);
const refreshing = ref(false);
// IM
const initIM = async () => {
if (!isIMInitialized.value) {
uni.showLoading({
title: "连接中...",
});
const success = await initIMAfterLogin(openid.value);
uni.hideLoading();
if (!success) {
uni.showToast({
title: "IM连接失败请重试",
icon: "none",
});
return false;
}
} else if (globalTimChatManager && !globalTimChatManager.isLoggedIn) {
uni.showLoading({
title: "重连中...",
});
const reconnected = await globalTimChatManager.ensureIMConnection();
uni.hideLoading();
if (!reconnected) {
return false;
}
}
return true;
};
//
const loadConversationList = async () => {
if (loading.value) return;
loading.value = true;
try {
console.log("开始加载群聊列表");
// globalTimChatManager
if (!globalTimChatManager || !globalTimChatManager.getGroupList) {
throw new Error("IM管理器未初始化");
}
// getGroupListSDK
const result = await globalTimChatManager.getGroupList();
if (result && result.success && result.groupList) {
conversationList.value = result.groupList
.map((group) => ({
conversationID: group.conversationID || `GROUP${group.groupID}`,
groupID: group.groupID,
name: group.patientName
? `${group.patientName}的问诊`
: group.name || "问诊群聊",
avatar: group.avatar || "/static/default-avatar.png",
lastMessage: group.lastMessage || "暂无消息",
lastMessageTime: group.lastMessageTime || Date.now(),
unreadCount: group.unreadCount || 0,
doctorId: group.doctorId,
patientName: group.patientName,
}))
.sort((a, b) => b.lastMessageTime - a.lastMessageTime);
console.log(
"群聊列表加载成功,共",
conversationList.value.length,
"个会话"
);
} else {
console.error("加载群聊列表失败:", result);
uni.showToast({
title: "加载失败,请重试",
icon: "none",
});
}
} catch (error) {
console.error("加载会话列表失败:", error);
uni.showToast({
title: error.message || "加载失败,请重试",
icon: "none",
});
} finally {
loading.value = false;
}
};
//
const extractMessagePreview = (message) => {
if (!message) return "暂无消息";
const payload = message.payload;
if (!payload) return "暂无消息";
//
if (message.type === "TIMTextElem") {
return payload.text || "暂无消息";
}
//
if (message.type === "TIMImageElem") {
return "[图片]";
}
//
if (message.type === "TIMSoundElem") {
return "[语音]";
}
//
if (message.type === "TIMVideoFileElem") {
return "[视频]";
}
//
if (message.type === "TIMFileElem") {
return "[文件]";
}
//
if (message.type === "TIMCustomElem") {
const description = payload.description;
if (description === "SYSTEM_NOTIFICATION") {
return "[系统消息]";
}
return "[消息]";
}
return "暂无消息";
};
//
const setupMessageListener = () => {
if (!globalTimChatManager) return;
globalTimChatManager.setCallback("onMessageReceived", (message) => {
console.log("消息列表页面收到新消息:", message);
//
const conversationID = message.conversationID;
const conversationIndex = conversationList.value.findIndex(
(conv) => conv.conversationID === conversationID
);
if (conversationIndex !== -1) {
//
const conversation = conversationList.value[conversationIndex];
conversation.lastMessage = extractMessagePreview(message);
conversation.lastMessageTime = message.lastTime || Date.now();
conversation.unreadCount = (conversation.unreadCount || 0) + 1;
//
const [updatedConversation] = conversationList.value.splice(
conversationIndex,
1
);
conversationList.value.unshift(updatedConversation);
console.log("已更新会话:", conversation.name);
}
});
};
//
const formatMessageTime = (timestamp) => {
if (!timestamp) return "";
const now = Date.now();
const diff = now - timestamp;
const date = new Date(timestamp);
// 1
if (diff < 60 * 1000) {
return "刚刚";
}
// 1
if (diff < 60 * 60 * 1000) {
return `${Math.floor(diff / (60 * 1000))}分钟前`;
}
//
const today = new Date();
if (date.toDateString() === today.toDateString()) {
return `${String(date.getHours()).padStart(2, "0")}:${String(
date.getMinutes()
).padStart(2, "0")}`;
}
//
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
if (date.toDateString() === yesterday.toDateString()) {
return "昨天";
}
//
if (date.getFullYear() === today.getFullYear()) {
return `${date.getMonth() + 1}${date.getDate()}`;
}
//
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}`;
};
//
const handleClickConversation = (conversation) => {
console.log("点击会话:", conversation);
//
uni.navigateTo({
url: `/pages/message/index?conversationID=${conversation.conversationID}&groupID=${conversation.groupID}`,
});
};
//
const handleLoadMore = () => {
if (loadingMore.value || !hasMore.value) return;
loadingMore.value = true;
// TODO:
setTimeout(() => {
loadingMore.value = false;
}, 1000);
};
//
const handleRefresh = async () => {
refreshing.value = true;
try {
await loadConversationList();
} finally {
refreshing.value = false;
}
};
//
onLoad(() => {
console.log("消息列表页面加载");
});
//
onShow(async () => {
try {
// IM
const imReady = await initIM();
if (!imReady) {
console.error("IM初始化失败");
return;
}
//
setupMessageListener();
//
await loadConversationList();
} catch (error) {
console.error("页面初始化失败:", error);
uni.showToast({
title: "初始化失败,请重试",
icon: "none",
});
}
});
//
onHide(() => {
//
if (globalTimChatManager) {
globalTimChatManager.setCallback("onMessageReceived", null);
}
});
</script>
<style>
</style>
<style scoped lang="scss">
.message-page {
width: 100%;
height: 100vh;
background-color: #f5f5f5;
}
.message-list {
width: 100%;
height: 100%;
}
.loading-container,
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.loading-text {
font-size: 28rpx;
color: #999;
}
.empty-image {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
.message-item {
display: flex;
align-items: center;
padding: 24rpx 32rpx;
background-color: #fff;
border-bottom: 1rpx solid #f0f0f0;
&:active {
background-color: #f5f5f5;
}
}
.avatar-container {
position: relative;
margin-right: 24rpx;
}
.avatar {
width: 96rpx;
height: 96rpx;
border-radius: 8rpx;
}
.unread-badge {
position: absolute;
top: -8rpx;
right: -8rpx;
min-width: 32rpx;
height: 32rpx;
padding: 0 8rpx;
background-color: #ff4d4f;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.unread-text {
font-size: 20rpx;
color: #fff;
line-height: 1;
}
.content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
min-width: 0;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8rpx;
}
.name {
font-size: 32rpx;
font-weight: 500;
color: #333;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.time {
font-size: 24rpx;
color: #999;
margin-left: 16rpx;
flex-shrink: 0;
}
.message-preview {
display: flex;
align-items: center;
}
.preview-text {
font-size: 28rpx;
color: #999;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.load-more {
padding: 20rpx 0;
text-align: center;
}
.load-more-text {
font-size: 24rpx;
color: #999;
}
</style>

View File

@ -2,7 +2,7 @@ import { ref } from "vue";
import { defineStore } from "pinia";
import api from '@/utils/api';
import { toast } from '@/utils/widget';
import { getInitIMPromise, clearInitIMPromise } from "@/utils/tim-chat.js";
import { initGlobalTIM, globalTimChatManager } from "@/utils/tim-chat.js";
const env = __VITE_ENV__;
@ -12,8 +12,10 @@ export default defineStore("accountStore", () => {
const loading = ref(false)
// IM 相关
const openid = ref("");
const isIMInitialized = ref(false);
// 医生信息
const doctorInfo = ref(null);
async function login(phoneCode = '') {
if (loading.value) return;
loading.value = true;
@ -38,6 +40,18 @@ export default defineStore("accountStore", () => {
account.value = res.data;
openid.value = res.data.openid;
await getDoctorInfo(openid.value);
// 登录成功后初始化腾讯IM
try {
console.log('开始初始化腾讯IMuserID:', res.data.openid);
await initGlobalTIM(res.data.openid);
isIMInitialized.value = true;
console.log('腾讯IM初始化成功');
} catch (imError) {
console.error('腾讯IM初始化失败:', imError);
// IM初始化失败不影响登录流程
}
return res.data
}
}
@ -47,6 +61,7 @@ export default defineStore("accountStore", () => {
}
loading.value = false
}
async function getDoctorInfo() {
try {
const res = await api('getCorpMemberData', {
@ -60,5 +75,39 @@ export default defineStore("accountStore", () => {
}
}
return { account, openid, doctorInfo, login, getDoctorInfo }
async function initIMAfterLogin(userID) {
if (isIMInitialized.value) {
return true;
}
try {
await initGlobalTIM(userID);
isIMInitialized.value = true;
return true;
} catch (error) {
console.error('IM初始化失败:', error);
return false;
}
}
// 退出登录
async function logout() {
try {
// 退出腾讯IM
if (globalTimChatManager && globalTimChatManager.tim) {
console.log('开始退出腾讯IM');
await globalTimChatManager.destroy();
console.log('腾讯IM退出成功');
}
} catch (error) {
console.error('退出腾讯IM失败:', error);
}
// 清空账户信息
account.value = null;
openid.value = "";
isIMInitialized.value = false;
doctorInfo.value = null;
}
return { account, openid, isIMInitialized, doctorInfo, login, getDoctorInfo, initIMAfterLogin, logout }
})

View File

@ -28,6 +28,11 @@ const urlsConfig = {
},
wecom: {
addContactWay: 'addContactWay'
},
im: {
getUserSig: 'getUserSig',
sendSystemMessage: "sendSystemMessage",
getChatRecordsByGroupId: "getChatRecordsByGroupId"
}
}

View File

@ -1,8 +1,7 @@
import {
checkGlobalIMStatus,
import {
checkGlobalIMStatus,
ensureGlobalIMConnection,
getGlobalIMLoginStatus,
setGlobalIMCallback
getGlobalIMLoginStatus
} from './tim-chat.js'
/**
@ -68,10 +67,10 @@ class IMStatusManager {
try {
console.log('执行IM状态检查...')
const isLoggedIn = checkGlobalIMStatus()
// 触发状态变化回调
this.triggerCallbacks('onStatusChange', {
isLoggedIn,
this.triggerCallbacks('onStatusChange', {
isLoggedIn,
checkTime: now,
timestamp: new Date().toLocaleString()
})
@ -97,26 +96,26 @@ class IMStatusManager {
try {
console.log('开始尝试IM重连...')
const success = await ensureGlobalIMConnection()
if (success) {
console.log('IM重连成功')
this.triggerCallbacks('onReconnectSuccess', {
timestamp: new Date().toLocaleString()
this.triggerCallbacks('onReconnectSuccess', {
timestamp: new Date().toLocaleString()
})
} else {
console.log('IM重连失败')
this.triggerCallbacks('onReconnectFailed', {
timestamp: new Date().toLocaleString()
this.triggerCallbacks('onReconnectFailed', {
timestamp: new Date().toLocaleString()
})
}
return success
} catch (error) {
console.error('IM重连异常:', error)
this.triggerCallbacks('onReconnectFailed', {
this.triggerCallbacks('onReconnectFailed', {
error,
timestamp: new Date().toLocaleString()
timestamp: new Date().toLocaleString()
})
return false
}
@ -206,7 +205,7 @@ class IMStatusManager {
report: {
isLoggedIn: status.isLoggedIn ? '已登录' : '未登录',
monitoring: status.isMonitoring ? '监控中' : '未监控',
lastCheck: status.lastCheckTime ?
lastCheck: status.lastCheckTime ?
new Date(status.lastCheckTime).toLocaleString() : '从未检查',
interval: `${status.checkInterval / 1000}`
}

File diff suppressed because it is too large Load Diff