新增回访计划聊天室
This commit is contained in:
parent
d75b8fb8cf
commit
fa01c77830
@ -279,6 +279,12 @@
|
||||
"style": {
|
||||
"navigationBarTitleText": "执行回访计划"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "followup-task-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "回访任务"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -121,6 +121,7 @@
|
||||
:archiveId="archiveId"
|
||||
:reachBottomTime="reachBottomTime"
|
||||
:floatingBottom="floatingBottom"
|
||||
:fromChat="fromChat"
|
||||
/>
|
||||
</view>
|
||||
|
||||
@ -242,6 +243,7 @@ const tabs = [
|
||||
const currentTab = ref('visitRecord');
|
||||
const reachBottomTime = ref(0);
|
||||
const archiveId = ref('');
|
||||
const fromChat = ref(false);
|
||||
const tabsScrollTop = ref(0);
|
||||
const instanceProxy = getCurrentInstance()?.proxy;
|
||||
|
||||
@ -474,6 +476,7 @@ async function updateArchive(patch) {
|
||||
|
||||
onLoad((options) => {
|
||||
archiveId.value = options?.id ? String(options.id) : String(archive.value.medicalRecordNo || archive.value.outpatientNo || archive.value.inpatientNo || '');
|
||||
fromChat.value = options?.fromChat === 'true' || options?.fromChat === true;
|
||||
|
||||
const cached = uni.getStorageSync(STORAGE_KEY);
|
||||
if (cached && typeof cached === 'object') {
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
</view>
|
||||
|
||||
<view class="list">
|
||||
<view v-for="i in list" :key="i._id" class="card" @click="toDetail(i)">
|
||||
<view v-for="i in list" :key="i._id" class="card">
|
||||
<view class="head">
|
||||
<view class="date">计划日期: <text class="date-val">{{ i.planDate }}</text></view>
|
||||
<view class="executor truncate">{{ i.executorName }}<text v-if="i.executeTeamName">({{ i.executeTeamName }})</text></view>
|
||||
@ -45,7 +45,13 @@
|
||||
</view>
|
||||
<view class="content">{{ i.taskContent || '暂无内容' }}</view>
|
||||
<view v-if="i.status === 'treated'" class="result">【处理结果】 {{ i.result || '' }}</view>
|
||||
<view class="footer">创建: {{ i.createTimeStr }} {{ i.creatorName }}</view>
|
||||
<view class="footer-row">
|
||||
<view class="footer">创建: {{ i.createTimeStr }} {{ i.creatorName }}</view>
|
||||
<button v-if="fromChat" class="action-btn send-btn" @click.stop="sendFollowUp(i)">发送</button>
|
||||
</view>
|
||||
<view class="card-actions">
|
||||
<button class="action-btn detail-btn" @click.stop="toDetail(i)">详情</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -146,6 +152,7 @@ const props = defineProps({
|
||||
archiveId: { type: String, default: '' },
|
||||
reachBottomTime: { type: [String, Number], default: '' },
|
||||
floatingBottom: { type: Number, default: 16 },
|
||||
fromChat: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
@ -390,6 +397,29 @@ function toDetail(todo) {
|
||||
uni.navigateTo({ url: `/pages/case/followup-detail?archiveId=${encodeURIComponent(props.archiveId)}&mode=edit&id=${encodeURIComponent(todo._id)}` });
|
||||
}
|
||||
|
||||
function sendFollowUp(todo) {
|
||||
const content = `【回访计划】\n类型: ${todo.eventTypeLabel}\n计划日期: ${todo.planDate}\n执行人: ${todo.executorName}\n内容: ${todo.taskContent || '暂无内容'}`;
|
||||
|
||||
// 触发事件,通知父组件发送消息
|
||||
uni.$emit('send-followup-message', {
|
||||
content,
|
||||
followupId: todo._id,
|
||||
followupData: todo
|
||||
});
|
||||
|
||||
// 获取当前页面栈
|
||||
const pages = getCurrentPages();
|
||||
const currentPage = pages[pages.length - 1];
|
||||
|
||||
// 如果当前页面是 followup-task-list,则返回两次(返回到消息页面)
|
||||
if (currentPage && currentPage.route === 'pages/case/followup-task-list') {
|
||||
uni.navigateBack({ delta: 2 });
|
||||
} else {
|
||||
// 否则只返回一次
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
|
||||
// ---- filter popup ----
|
||||
const filterPopupRef = ref(null);
|
||||
const state = ref(null);
|
||||
@ -625,10 +655,60 @@ watch(
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
.footer {
|
||||
.footer-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #f2f2f2;
|
||||
gap: 10px;
|
||||
}
|
||||
.footer {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
flex: 1;
|
||||
}
|
||||
.footer-row .send-btn {
|
||||
flex: 0 0 auto;
|
||||
width: 60px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f2f2f2;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
border: none;
|
||||
background: #f5f6f8;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.action-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.detail-btn {
|
||||
background: #f5f6f8;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
background: #0877F1;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.empty {
|
||||
|
||||
74
pages/case/followup-task-list.vue
Normal file
74
pages/case/followup-task-list.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<view class="followup-task-page">
|
||||
<!-- 回访任务列表组件 -->
|
||||
<FollowUpManageTab
|
||||
:data="patientData"
|
||||
:archiveId="archiveId"
|
||||
:reachBottomTime="reachBottomTime"
|
||||
:floatingBottom="0"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import FollowUpManageTab from "./components/archive-detail/follow-up-manage-tab.vue";
|
||||
|
||||
const archiveId = ref("");
|
||||
const patientData = ref({});
|
||||
const reachBottomTime = ref("");
|
||||
|
||||
onLoad((options) => {
|
||||
archiveId.value = options.archiveId || "";
|
||||
patientData.value = {
|
||||
name: options.patientName || "",
|
||||
};
|
||||
});
|
||||
|
||||
const handleBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.followup-task-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 44px;
|
||||
background: #0877f1;
|
||||
padding: 0 14px;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: -14px;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav-placeholder {
|
||||
width: 44px;
|
||||
}
|
||||
</style>
|
||||
@ -315,6 +315,8 @@ const closePreview = () => {
|
||||
const sendArticle = async (article) => {
|
||||
try {
|
||||
const { doctorInfo } = useAccountStore();
|
||||
|
||||
// 1. 调用后端API记录发送
|
||||
const result = await api("sendArticleMessage", {
|
||||
groupId: pageParams.value.groupId,
|
||||
fromAccount: doctorInfo.userid,
|
||||
@ -323,8 +325,46 @@ const sendArticle = async (article) => {
|
||||
imgUrl: article.cover || "",
|
||||
desc: "点击查看详情",
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
// 记录文章发送记录
|
||||
// 2. 通过IM系统发送自定义消息到聊天列表
|
||||
try {
|
||||
// 获取全局IM管理器
|
||||
const { globalTimChatManager } = await import("@/utils/tim-chat.js");
|
||||
|
||||
if (globalTimChatManager && globalTimChatManager.tim && globalTimChatManager.isLoggedIn) {
|
||||
// 重要:设置当前会话ID,确保消息发送到正确的群聊
|
||||
const conversationID = `GROUP${pageParams.value.groupId}`;
|
||||
globalTimChatManager.currentConversationID = conversationID;
|
||||
|
||||
console.log("设置当前会话ID:", conversationID);
|
||||
|
||||
// 构建自定义消息数据
|
||||
const customMessageData = {
|
||||
messageType: "article",
|
||||
title: article.title || "宣教文章",
|
||||
articleId: article._id,
|
||||
cover: article.cover || "",
|
||||
desc: "点击查看详情",
|
||||
content: article.title || "宣教文章"
|
||||
};
|
||||
|
||||
// 发送自定义消息
|
||||
const sendResult = await globalTimChatManager.sendCustomMessage(customMessageData);
|
||||
if (sendResult && sendResult.success) {
|
||||
console.log("✓ 文章消息已通过IM系统发送");
|
||||
} else {
|
||||
console.warn("⚠️ 文章消息发送失败:", sendResult?.error);
|
||||
}
|
||||
} else {
|
||||
console.warn("⚠️ IM系统未就绪,消息可能不会显示在聊天列表");
|
||||
}
|
||||
} catch (imError) {
|
||||
console.error("通过IM系统发送消息失败:", imError);
|
||||
// IM发送失败不影响后端记录,继续返回
|
||||
}
|
||||
|
||||
// 3. 记录文章发送记录
|
||||
try {
|
||||
await api("addArticleSendRecord", {
|
||||
articleId: article._id,
|
||||
|
||||
@ -430,6 +430,15 @@ $primary-color: #0877F1;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.text-input {
|
||||
padding: 16rpx 46rpx;
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.voice-input-btn {
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<textarea v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..."
|
||||
@confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput"
|
||||
:auto-height="true" :show-confirm-bar="false" :adjust-position="true"
|
||||
placeholder-style="line-height: 80rpx;" />
|
||||
/>
|
||||
<input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord"
|
||||
@touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord" :placeholder="isRecording ? '松开发送' : '按住说话'" disabled>
|
||||
</input>
|
||||
@ -365,6 +365,13 @@ const goToCommonPhrases = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 打开回访任务列表
|
||||
const showFollowUpTasks = () => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/case/followup-task-list?archiveId=${props.patientId}&patientName=${props.patientInfo.name}`,
|
||||
});
|
||||
};
|
||||
|
||||
// 跳转到宣教文章页面
|
||||
const goToArticleList = () => {
|
||||
uni.navigateTo({
|
||||
@ -402,7 +409,7 @@ const morePanelButtons = [
|
||||
{
|
||||
text: "回访任务",
|
||||
icon: "/static/icon/huifangrenwu.png",
|
||||
action: showImagePicker,
|
||||
action: showFollowUpTasks,
|
||||
},
|
||||
{
|
||||
text: "常用语",
|
||||
|
||||
@ -757,8 +757,34 @@ onShow(() => {
|
||||
}
|
||||
|
||||
startIMMonitoring(30000);
|
||||
|
||||
// 监听回访任务发送事件
|
||||
uni.$on('send-followup-message', handleSendFollowUpMessage);
|
||||
});
|
||||
|
||||
// 处理发送回访任务消息
|
||||
const handleSendFollowUpMessage = async (data) => {
|
||||
try {
|
||||
if (chatInputRef.value) {
|
||||
// 将回访计划内容设置到输入框
|
||||
chatInputRef.value.setInputText(data.content);
|
||||
|
||||
// 延迟后自动发送
|
||||
setTimeout(() => {
|
||||
if (chatInputRef.value) {
|
||||
chatInputRef.value.sendTextMessageFromPhrase(data.content);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送回访任务消息失败:', error);
|
||||
uni.showToast({
|
||||
title: '发送失败,请重试',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 页面隐藏
|
||||
onHide(() => {
|
||||
stopIMMonitoring();
|
||||
@ -945,6 +971,9 @@ onUnmounted(() => {
|
||||
timChatManager.setCallback("onMessageReceived", null);
|
||||
timChatManager.setCallback("onMessageListLoaded", null);
|
||||
timChatManager.setCallback("onError", null);
|
||||
|
||||
// 移除回访任务发送事件监听
|
||||
uni.$off('send-followup-message', handleSendFollowUpMessage);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@ -723,14 +723,26 @@ class TimChatManager {
|
||||
|
||||
// 判断是否为当前会话的消息
|
||||
// 系统消息:只要会话ID匹配就显示(不要求必须有currentConversationID)
|
||||
// 自定义消息(文章等):如果currentConversationID已设置,则需要匹配;如果未设置,则暂存等待
|
||||
// 普通消息:必须有currentConversationID且匹配才显示
|
||||
const isCurrentConversation = isSystemMsg
|
||||
? messageConversationID === this.currentConversationID
|
||||
: (this.currentConversationID && messageConversationID === this.currentConversationID)
|
||||
let isCurrentConversation = false
|
||||
|
||||
if (isSystemMsg) {
|
||||
// 系统消息:只要会话ID匹配就显示
|
||||
isCurrentConversation = messageConversationID === this.currentConversationID
|
||||
} else if (convertedMessage.type === 'TIMCustomElem') {
|
||||
// 自定义消息(包括文章):如果currentConversationID已设置,则需要匹配;否则也接收
|
||||
// 这样可以确保消息不会丢失,即使用户还没进入聊天页面
|
||||
isCurrentConversation = !this.currentConversationID || messageConversationID === this.currentConversationID
|
||||
} else {
|
||||
// 普通消息:必须有currentConversationID且匹配才显示
|
||||
isCurrentConversation = this.currentConversationID && messageConversationID === this.currentConversationID
|
||||
}
|
||||
|
||||
console.log('消息会话匹配检查:', {
|
||||
isCurrentConversation,
|
||||
isSystemMessage: isSystemMsg,
|
||||
messageType: convertedMessage.type,
|
||||
hasCurrentConversationID: !!this.currentConversationID,
|
||||
conversationIDMatch: messageConversationID === this.currentConversationID
|
||||
})
|
||||
@ -2516,32 +2528,66 @@ class TimChatManager {
|
||||
formatCustomMessage(payload) {
|
||||
try {
|
||||
if (!payload || !payload.data) {
|
||||
console.warn('payload.data 为空:', payload)
|
||||
return '[自定义消息]'
|
||||
}
|
||||
|
||||
const customData = typeof payload.data === 'string'
|
||||
? JSON.parse(payload.data)
|
||||
: payload.data
|
||||
let customData
|
||||
try {
|
||||
customData = typeof payload.data === 'string'
|
||||
? JSON.parse(payload.data)
|
||||
: payload.data
|
||||
} catch (parseError) {
|
||||
console.error('JSON 解析失败:', payload.data, parseError)
|
||||
return payload.description || '[自定义消息]'
|
||||
}
|
||||
|
||||
const messageType = customData.messageType || customData.type
|
||||
|
||||
const messageTypeMap = {
|
||||
system_message: '[系统消息]',
|
||||
symptom: '[病情描述]',
|
||||
prescription: '[处方单]',
|
||||
refill: '[续方申请]',
|
||||
survey: '[问卷调查]',
|
||||
article: '[文章]',
|
||||
consult_pending: '患者向团队发起咨询,请在1小时内接诊',
|
||||
consult_rejected: '咨询已被拒绝',
|
||||
consult_timeout: '咨询已超时自动关闭',
|
||||
consult_accepted: '已接诊,会话已开始',
|
||||
consult_ended: '已结束当前会话',
|
||||
// 使用 switch 语句保持与 getGroupListInternal 中的逻辑一致
|
||||
let messageText = '[自定义消息]'
|
||||
switch (messageType) {
|
||||
case 'system_message':
|
||||
messageText = '[系统消息]'
|
||||
break
|
||||
case 'symptom':
|
||||
messageText = '[病情描述]'
|
||||
break
|
||||
case 'prescription':
|
||||
messageText = '[处方单]'
|
||||
break
|
||||
case 'refill':
|
||||
messageText = '[续方申请]'
|
||||
break
|
||||
case 'survey':
|
||||
messageText = '[问卷调查]'
|
||||
break
|
||||
case 'article':
|
||||
messageText = '[文章]'
|
||||
break
|
||||
case 'consult_pending':
|
||||
messageText = '患者向团队发起咨询,请在1小时内接诊,超时将自动关闭会话'
|
||||
break
|
||||
case 'consult_rejected':
|
||||
messageText = '患者向团队发起咨询,由于有紧急事务要处理暂时无法接受咨询.本次会话已关闭'
|
||||
break
|
||||
case 'consult_timeout':
|
||||
messageText = '患者向团队发起咨询,团队成员均未接受咨询,本次会话已自动关闭'
|
||||
break
|
||||
case 'consult_accepted':
|
||||
messageText = '已接诊,会话已开始'
|
||||
break
|
||||
case 'consult_ended':
|
||||
messageText = '已结束当前会话'
|
||||
break
|
||||
default:
|
||||
messageText = customData.content || '[自定义消息]'
|
||||
}
|
||||
|
||||
return messageTypeMap[messageType] || customData.content || '[自定义消息]'
|
||||
console.log('自定义消息类型:', messageType, '显示文本:', messageText)
|
||||
return messageText
|
||||
} catch (error) {
|
||||
console.error('格式化自定义消息失败:', error)
|
||||
console.error('格式化自定义消息失败:', error, payload)
|
||||
return '[自定义消息]'
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user