更新
This commit is contained in:
@@ -46,7 +46,7 @@ const wxcode = async (code) => {
|
||||
uni.setStorageSync('token', res.data.token)
|
||||
uni.setStorageSync('userData', res.data)
|
||||
if(res.code==1 &&res.data.diagnosis){
|
||||
video(res.data)
|
||||
//video(res.data)
|
||||
}
|
||||
console.log('请求结果:', res.data)
|
||||
} catch (err) {
|
||||
@@ -69,7 +69,7 @@ const userinfo = async (code) => {
|
||||
}
|
||||
uni.setStorageSync('userData', res.data)
|
||||
if(res.code==1 && res.data.diagnosis){
|
||||
video(res.data)
|
||||
//video(res.data)
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
|
||||
@@ -14,6 +14,28 @@
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<view class="message-container">
|
||||
<!-- 医生卡片(视频通话开始或结束时自动隐藏) -->
|
||||
<view v-if="doctorInfo && showDoctorCard" class="doctor-card" @click="goDoctorDetail">
|
||||
<view class="doctor-card-main">
|
||||
<view class="doctor-card-avatar-wrap">
|
||||
<image :src="doctorInfo.avatar || '/static/user/user.png'" class="doctor-card-avatar" mode="aspectFill" />
|
||||
<view class="doctor-card-status-dot"></view>
|
||||
</view>
|
||||
<view class="doctor-card-info">
|
||||
<text class="doctor-card-name">{{ doctorInfo.name || '医生' }}</text>
|
||||
<text class="doctor-card-title">{{ doctorInfo.title || '主任医师' }}</text>
|
||||
<text class="doctor-card-hospital">{{ doctorInfo.hospital || '甄养堂医院' }}</text>
|
||||
</view>
|
||||
<view class="doctor-card-timer">
|
||||
<!-- <text class="doctor-card-timer-value">{{ formatDoctorWaitTime(doctorWaitSeconds) }}</text> -->
|
||||
<text class="doctor-card-timer-label">等待中</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="doctor-card-tip">
|
||||
<text class="doctor-card-tip-icon">⏳</text>
|
||||
<text class="doctor-card-tip-text">请等待医生,医生即将进入诊室</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-for="(msg, index) in messages" :key="index" class="message-wrapper" :class="msg.flow === 'out' ? 'message-wrapper-out' : 'message-wrapper-in'">
|
||||
<!-- 头像 -->
|
||||
<!-- 头像 -->
|
||||
@@ -84,16 +106,16 @@
|
||||
<text class="tool-label">表情</text>
|
||||
</view> -->
|
||||
<view class="tool-item tool-item-left" @click="toggleInputMode">
|
||||
<uni-icons :type="inputMode === 'voice' ? 'compose' : 'mic'" size="38" color="#576b95"></uni-icons>
|
||||
<uni-icons :type="inputMode === 'voice' ? 'compose' : 'mic'" size="28" color="#576b95"></uni-icons>
|
||||
<text class="tool-label" v-if="inputMode === 'voice'">文字</text>
|
||||
<text class="tool-label" v-else>语音</text>
|
||||
</view>
|
||||
<view class="tool-item" @click="chooseImage">
|
||||
<uni-icons type="image " size="40" color="#576b95"></uni-icons>
|
||||
<uni-icons type="image " size="30" color="#576b95"></uni-icons>
|
||||
<text class="tool-label">图片</text>
|
||||
</view>
|
||||
<view class="tool-item" @click="chooseFile">
|
||||
<uni-icons type="paperclip " size="40" color="#576b95"></uni-icons>
|
||||
<uni-icons type="paperclip " size="30" color="#576b95"></uni-icons>
|
||||
<text class="tool-label">文件</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -181,20 +203,27 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, nextTick } from 'vue';
|
||||
import { onLoad, onUnload } from '@dcloudio/uni-app';
|
||||
import { ref, nextTick, getCurrentInstance } from 'vue';
|
||||
import { onLoad, onShow, onUnload, onHide } from '@dcloudio/uni-app';
|
||||
import TencentCloudChat from '@tencentcloud/lite-chat/basic';
|
||||
import conversationPlugin from '@tencentcloud/lite-chat/plugins/conversation';
|
||||
import messageEnhancerPlugin from '@tencentcloud/lite-chat/plugins/message-enhancer';
|
||||
import richMediaMessagePlugin from '@tencentcloud/lite-chat/plugins/rich-media-message';
|
||||
import groupPlugin from '@tencentcloud/lite-chat/plugins/group';
|
||||
|
||||
import * as GenerateTestUserSig from "@/debug/GenerateTestUserSig-es.js";
|
||||
import { CallManager } from "@/TUICallKit/src/TUICallService/serve/callManager";
|
||||
uni.CallManager = new CallManager();
|
||||
const { proxy } = getCurrentInstance()
|
||||
const messages = ref([]);
|
||||
const inputText = ref('');
|
||||
const scrollTop = ref(0);
|
||||
const showEmoji = ref(false);
|
||||
const keyboardHeight = ref(0);
|
||||
const userAvatar = ref(''); // 当前用户头像
|
||||
const doctorInfo = ref(null); // 医生信息(对方为医生时展示)
|
||||
const showDoctorCard = ref(true); // 是否显示医生卡片(通话开始/结束时隐藏)
|
||||
const doctorWaitSeconds = ref(0); // 等待医生计时(秒)
|
||||
let doctorWaitTimer = null; // 等待计时器
|
||||
const inputMode = ref('text'); // 'text' | 'voice'
|
||||
const isRecording = ref(false);
|
||||
const recordingDuration = ref(0);
|
||||
@@ -212,6 +241,12 @@
|
||||
let currentUserID = ''; // 当前登录用户ID
|
||||
let scrollTimer = null; // 滚动定时器
|
||||
|
||||
const CONFIRMATION_MESSAGE = `我已确认以下信息:
|
||||
1、本人确认已在线下确诊;
|
||||
2、本人确认问诊单信息无误;
|
||||
3、本人确认已知晓病情评估风险告知;
|
||||
4、本人确认已阅读并同意《互联网诊疗知情同意书》。`;
|
||||
|
||||
const emojiList = [
|
||||
'😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂',
|
||||
'🙂', '🙃', '😉', '😊', '😇', '🥰', '😍', '🤩',
|
||||
@@ -463,17 +498,51 @@
|
||||
innerAudioContext = null;
|
||||
uni.showToast({ title: '播放失败', icon: 'none' });
|
||||
});
|
||||
}
|
||||
const video =async (userid)=>{
|
||||
|
||||
const uid=userid.replace('patient_', '')
|
||||
console.log('userid',userid,uid)
|
||||
const res = await proxy.apiUrl({
|
||||
url: '/api/tcm/getPatientSignature',
|
||||
method: 'GET',
|
||||
data: {
|
||||
patient_id: uid
|
||||
},
|
||||
}, false)
|
||||
|
||||
const {userId } = res.data;
|
||||
|
||||
const { userSig, SDKAppID } = GenerateTestUserSig.genTestUserSig({
|
||||
userID:userId,
|
||||
});
|
||||
|
||||
getApp().globalData.userID = userId;
|
||||
getApp().globalData.userSig = userSig;
|
||||
getApp().globalData.SDKAppID = SDKAppID;
|
||||
const cc={
|
||||
sdkAppID: SDKAppID, // 替换为用户自己的 sdkAppID
|
||||
userID: userId, // 替换为用户自己的 userID
|
||||
userSig: userSig, // 替换为用户自己的 userSig
|
||||
globalCallPagePath: "TUICallKit/src/Components/TUICallKit", // 替换为步骤一里注册的全局监听页面
|
||||
}
|
||||
uni.setStorageSync('CallManager', userId)
|
||||
await uni.CallManager.init(cc);
|
||||
}
|
||||
|
||||
onLoad(async (options) => {
|
||||
try {
|
||||
console.log('进入onMounted:', options);
|
||||
const app = getApp();
|
||||
const userID = app.globalData.userID || options.userID;
|
||||
const userSig = app.globalData.userSig || options.userSig;
|
||||
const SDKAppID = app.globalData.SDKAppID || options.SDKAppID;
|
||||
const userID = options.userID|| app.globalData.userID;
|
||||
if(!userID.includes('patient')){
|
||||
userID='patient_'+userID
|
||||
}
|
||||
await video(userID)
|
||||
const userSig = options.userSig||app.globalData.userSig;
|
||||
const SDKAppID = options.SDKAppID|| app.globalData.SDKAppID ;
|
||||
targetUserID = options.targetUserID;
|
||||
currentUserID = userID; // 保存当前用户ID
|
||||
console.log('-------',userID,userSig,SDKAppID)
|
||||
console.log('currentUserID',currentUserID)
|
||||
// 获取用户头像
|
||||
const userData = uni.getStorageSync('userData');
|
||||
if (userData && userData.avatar) {
|
||||
@@ -487,8 +556,22 @@
|
||||
}
|
||||
|
||||
conversationID = `C2C${targetUserID}`;
|
||||
console.log('当前用户:', currentUserID, '对方用户:', targetUserID,'conversationID'+conversationID);
|
||||
|
||||
console.log('currentUserID:', currentUserID, '对方用户:', targetUserID,'conversationID'+conversationID);
|
||||
|
||||
console.log('onMounted',userID,userSig,SDKAppID,conversationID)
|
||||
// 切换账户前先登出,避免 IM 登录用户不匹配
|
||||
const prevUserID = app.globalData.imChatUserID;
|
||||
if (app.globalData.imChat && prevUserID !== userID) {
|
||||
try {
|
||||
app.globalData.imChat.off?.(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
|
||||
await app.globalData.imChat.logout();
|
||||
await app.globalData.imChat.destroy?.();
|
||||
} catch (e) {
|
||||
console.warn('IM logout error:', e);
|
||||
}
|
||||
app.globalData.imChat = null;
|
||||
app.globalData.imChatUserID = null;
|
||||
}
|
||||
if (!app.globalData.imChat) {
|
||||
chat = TencentCloudChat.create({
|
||||
SDKAppID: Number(SDKAppID),
|
||||
@@ -500,18 +583,30 @@
|
||||
chat.use(richMediaMessagePlugin);
|
||||
chat.use(groupPlugin);
|
||||
|
||||
await chat.login({ userID, userSig });
|
||||
let promise = chat.updateMyProfile({
|
||||
nick: userData.nickname,
|
||||
avatar: userData.avatar,
|
||||
gender: TencentCloudChat.TYPES.GENDER_MALE,
|
||||
})
|
||||
promise.then(function(imResponse) {
|
||||
console.log('sssssssssssssssssssss',imResponse.data,userData.avatar); // 更新资料成功
|
||||
}).catch(function(imError) {
|
||||
console.warn('updateMyProfile error:', imError); // 更新资料失败的相关信息
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
// updateMyProfile 非必需,失败不阻塞聊天(如 2024 用户未登录时可忽略)
|
||||
try {
|
||||
await chat.updateMyProfile({
|
||||
avatar: userData?.avatar,
|
||||
gender: TencentCloudChat.TYPES.GENDER_MALE,
|
||||
});
|
||||
} catch (profileErr) {
|
||||
console.warn('updateMyProfile error:', profileErr);
|
||||
}
|
||||
app.globalData.imChat = chat;
|
||||
app.globalData.imChatUserID = userID;
|
||||
chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
|
||||
} else {
|
||||
chat = app.globalData.imChat;
|
||||
@@ -519,6 +614,14 @@
|
||||
}
|
||||
|
||||
await loadHistoryMessages();
|
||||
|
||||
if(options.msg==1){
|
||||
console.log('msg我进来了',options.msg)
|
||||
await sendConfirmationMessage(); // 进入聊天时自动发送知情确认
|
||||
}
|
||||
loadDoctorInfo(); // 加载医生卡片信息(对方为医生时)
|
||||
uni.$on('TUICall:callStart', onVideoCallStart);
|
||||
uni.$on('TUICall:callEnd', onVideoCallEnd);
|
||||
//uni.showToast({ title: '聊天已就绪', icon: 'success' });
|
||||
|
||||
// 监听键盘高度变化
|
||||
@@ -540,7 +643,25 @@
|
||||
}
|
||||
});
|
||||
|
||||
onHide(() => {
|
||||
const pages = getCurrentPages();
|
||||
const top = pages[pages.length - 1];
|
||||
if (top?.route?.includes('TUICallKit')) {
|
||||
getApp().globalData._chatFromCallPage = true;
|
||||
}
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
if (getApp().globalData._chatFromCallPage) {
|
||||
getApp().globalData._chatFromCallPage = false;
|
||||
hideDoctorCardByCall();
|
||||
}
|
||||
});
|
||||
|
||||
onUnload(() => {
|
||||
uni.$off('TUICall:callStart', onVideoCallStart);
|
||||
uni.$off('TUICall:callEnd', onVideoCallEnd);
|
||||
stopDoctorWaitTimer();
|
||||
if (chat) chat.off(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
|
||||
if (scrollTimer) clearTimeout(scrollTimer);
|
||||
if (innerAudioContext) {
|
||||
@@ -555,6 +676,7 @@
|
||||
async function loadHistoryMessages() {
|
||||
try {
|
||||
const res = await chat.getMessageList({ conversationID, count: 15 });
|
||||
console.log('消息',conversationID);
|
||||
if (res.data && res.data.messageList) {
|
||||
// SDK返回的消息是从新到旧,reverse后变成从旧到新,最新消息在底部
|
||||
const parsedMessages = res.data.messageList
|
||||
@@ -700,6 +822,24 @@
|
||||
uni.showToast({ title: '发送失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
|
||||
async function sendConfirmationMessage() {
|
||||
const alreadySent = messages.value.some(m => m.type === 'text' && m.text === CONFIRMATION_MESSAGE);
|
||||
if (alreadySent) return;
|
||||
try {
|
||||
const message = chat.createTextMessage({
|
||||
to: targetUserID,
|
||||
conversationType: TencentCloudChat.TYPES.CONV_C2C,
|
||||
payload: { text: CONFIRMATION_MESSAGE }
|
||||
});
|
||||
await chat.sendMessage(message);
|
||||
messages.value.push({ type: 'text', text: CONFIRMATION_MESSAGE, time: Date.now(), flow: 'out', avatar: userAvatar.value });
|
||||
scrollToBottom();
|
||||
setTimeout(() => scrollToBottom(), 150);
|
||||
} catch (error) {
|
||||
console.error('发送确认信息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function chooseImage() {
|
||||
// #ifdef MP-WEIXIN
|
||||
@@ -876,6 +1016,7 @@
|
||||
|
||||
async function makeVideoCall() {
|
||||
try {
|
||||
hideDoctorCardByCall();
|
||||
if (!uni.CallManager) {
|
||||
uni.showToast({ title: '通话功能未初始化', icon: 'none' });
|
||||
return;
|
||||
@@ -887,6 +1028,69 @@
|
||||
}
|
||||
}
|
||||
|
||||
function startVideoCall() {
|
||||
makeVideoCall();
|
||||
}
|
||||
|
||||
async function loadDoctorInfo() {
|
||||
if (!targetUserID || !targetUserID.startsWith('doctor_')) return;
|
||||
const doctorId = targetUserID.replace('doctor_', '');
|
||||
try {
|
||||
const res = await proxy.apiUrl({
|
||||
url: '/api/doctor/detail',
|
||||
method: 'GET',
|
||||
data: { id: doctorId }
|
||||
}, false);
|
||||
if (res && res.code === 1 && res.data) {
|
||||
doctorInfo.value = res.data;
|
||||
startDoctorWaitTimer();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('加载医生信息失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function formatDoctorWaitTime(seconds) {
|
||||
const m = Math.floor(seconds / 60);
|
||||
const s = seconds % 60;
|
||||
return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function startDoctorWaitTimer() {
|
||||
stopDoctorWaitTimer();
|
||||
doctorWaitSeconds.value = 0;
|
||||
doctorWaitTimer = setInterval(() => {
|
||||
doctorWaitSeconds.value++;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopDoctorWaitTimer() {
|
||||
if (doctorWaitTimer) {
|
||||
clearInterval(doctorWaitTimer);
|
||||
doctorWaitTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function goDoctorDetail() {
|
||||
if (!doctorInfo.value?.id) return;
|
||||
uni.navigateTo({ url: `/doctor/pages/doctor/doctor?id=${doctorInfo.value.id}` });
|
||||
}
|
||||
|
||||
function hideDoctorCardByCall() {
|
||||
showDoctorCard.value = false;
|
||||
stopDoctorWaitTimer();
|
||||
}
|
||||
|
||||
function onVideoCallStart() {
|
||||
hideDoctorCardByCall();
|
||||
}
|
||||
|
||||
function onVideoCallEnd() {
|
||||
hideDoctorCardByCall();
|
||||
uni.showToast({ title: '视频通话已结束', icon: 'success' });
|
||||
}
|
||||
|
||||
|
||||
function onInputFocus() {
|
||||
console.log('输入框获得焦点');
|
||||
showEmoji.value = false;
|
||||
@@ -1024,6 +1228,106 @@
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
/* 医生卡片 */
|
||||
.doctor-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 32rpx;
|
||||
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%);
|
||||
border-radius: 24rpx;
|
||||
border: 2rpx solid #e8ecf0;
|
||||
box-shadow: 0 4rpx 20rpx rgba(24, 144, 255, 0.06);
|
||||
overflow: hidden;
|
||||
}
|
||||
.doctor-card-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 32rpx;
|
||||
}
|
||||
.doctor-card-avatar-wrap {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.doctor-card-avatar {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #e8ecf0;
|
||||
}
|
||||
.doctor-card-status-dot {
|
||||
position: absolute;
|
||||
right: 4rpx;
|
||||
bottom: 4rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: #52c41a;
|
||||
border: 4rpx solid #fff;
|
||||
border-radius: 50%;
|
||||
animation: doctorStatusPulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
@keyframes doctorStatusPulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.7; transform: scale(1.1); }
|
||||
}
|
||||
.doctor-card-info {
|
||||
flex: 1;
|
||||
margin-left: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
.doctor-card-name {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
.doctor-card-title {
|
||||
font-size: 26rpx;
|
||||
color: #576b95;
|
||||
}
|
||||
.doctor-card-hospital {
|
||||
font-size: 24rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
.doctor-card-timer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4rpx;
|
||||
padding: 16rpx 28rpx;
|
||||
background: linear-gradient(135deg, rgba(24, 144, 255, 0.12) 0%, rgba(24, 144, 255, 0.06) 100%);
|
||||
border-radius: 20rpx;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
.doctor-card-timer-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #1890ff;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.doctor-card-timer-label {
|
||||
font-size: 22rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
.doctor-card-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
background: linear-gradient(90deg, rgba(24, 144, 255, 0.08) 0%, rgba(24, 144, 255, 0.04) 100%);
|
||||
border-top: 2rpx dashed rgba(24, 144, 255, 0.15);
|
||||
}
|
||||
.doctor-card-tip-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
.doctor-card-tip-text {
|
||||
font-size: 28rpx;
|
||||
color: #576b95;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.message-wrapper {
|
||||
display: flex;
|
||||
margin-bottom: 32rpx;
|
||||
@@ -1095,7 +1399,7 @@
|
||||
|
||||
/* 文本消息 - 大字号 */
|
||||
.message-text {
|
||||
font-size: 38rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.6;
|
||||
color: #1a1a1a;
|
||||
word-break: break-all;
|
||||
|
||||
@@ -175,9 +175,17 @@ export default class EngineEventHandler {
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_TIPS, { text: 'answered', duration: 2000 });
|
||||
await this._callService.openMicrophone();
|
||||
console.log(`${NAME.PREFIX}accept event data: ${JSON.stringify(event)}.`);
|
||||
// 通知聊天页:视频/语音通话已接通,可关闭等待计时器
|
||||
try {
|
||||
uni.$emit('TUICall:callStart', event);
|
||||
} catch (_) {}
|
||||
}
|
||||
private async _handleUserEnter(event: any): Promise<void> {
|
||||
const { userID: userId, data } = analyzeEventData(event);
|
||||
// 对方进入房间,通话已接通,通知聊天页关闭等待计时器
|
||||
try {
|
||||
uni.$emit('TUICall:callStart', event);
|
||||
} catch (_) {}
|
||||
|
||||
if (userId && TUIStore.getData(StoreName.CALL, NAME.CALL_ROLE === CallRole.CALLEE)) {
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) === CallStatus.CALLING) {
|
||||
@@ -232,6 +240,10 @@ export default class EngineEventHandler {
|
||||
private _handleCallingEnd(event: any): void {
|
||||
console.log(`${NAME.PREFIX}callEnd event data: ${JSON.stringify(event)}.`);
|
||||
this._callService?._resetCallStore();
|
||||
// 通知聊天页等:视频通话已结束,可关闭等待计时器等
|
||||
try {
|
||||
uni.$emit('TUICall:callEnd', event);
|
||||
} catch (_) {}
|
||||
}
|
||||
private _handleKickedOut(event: any): void {
|
||||
console.log(`${NAME.PREFIX}kickOut event data: ${JSON.stringify(event)}.`);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-value">{{ formatPatientCount(doctor.patient_count) }}</text>
|
||||
<text class="stat-value">100{{ formatPatientCount(doctor.patient_count) }}</text>
|
||||
<text class="stat-label">患者数</text>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
@@ -51,6 +51,21 @@
|
||||
</view>
|
||||
</section>
|
||||
|
||||
<!-- 医生资质 -->
|
||||
<section class="qualification-section" v-if="qualificationImages.length > 0">
|
||||
<text class="section-title-plain">医生资质</text>
|
||||
<view class="qualification-grid">
|
||||
<image
|
||||
v-for="(img, idx) in qualificationImages"
|
||||
:key="idx"
|
||||
:src="img"
|
||||
class="qualification-image"
|
||||
mode="aspectFill"
|
||||
@click="previewImage(img)"
|
||||
/>
|
||||
</view>
|
||||
</section>
|
||||
|
||||
<!-- 专业领域 -->
|
||||
<section class="expertise-section">
|
||||
<text class="section-title-plain">专业领域</text>
|
||||
@@ -108,16 +123,55 @@
|
||||
<view v-if="doctor">
|
||||
<!-- 底部操作栏 -->
|
||||
<view v-if="doctor.enable_image_consult || doctor.enable_video_consult" class="bottom-bar">
|
||||
<view class="btn-chat" @click="goChat" v-if="doctor.enable_image_consult">
|
||||
<view class="btn-chat" @click="showCardSelector('chat')" v-if="doctor.enable_image_consult">
|
||||
<text class="btn-icon">💬</text>
|
||||
<text class="btn-text">图文问诊</text>
|
||||
</view>
|
||||
<view class="btn-video" @click="goVideo" v-if="doctor.enable_video_consult">
|
||||
<view class="btn-video" @click="showCardSelector('video')" v-if="doctor.enable_video_consult">
|
||||
<text class="btn-icon">📹</text>
|
||||
<text class="btn-text">视频问诊</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 就诊人选择弹窗 -->
|
||||
<view v-if="showCardModal" class="modal-overlay" @click="closeCardModal">
|
||||
<view class="modal-content" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">选择就诊人</text>
|
||||
<text class="modal-close" @click="closeCardModal">✕</text>
|
||||
</view>
|
||||
|
||||
<view class="modal-body">
|
||||
<!-- 加载中 -->
|
||||
<view v-if="loadingCards" class="modal-loading">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 就诊卡列表 -->
|
||||
<view v-else-if="cardList.length > 0" class="card-list">
|
||||
<view
|
||||
v-for="card in cardList"
|
||||
:key="card.id"
|
||||
class="card-item"
|
||||
@click="selectCard(card)"
|
||||
>
|
||||
<view class="card-info">
|
||||
<text class="card-name">{{ card.patient_name }}</text>
|
||||
<text class="card-detail">{{ card.gender_desc }} / {{ card.age }}岁</text>
|
||||
</view>
|
||||
<text class="card-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-else class="modal-empty">
|
||||
<text class="empty-text">暂无就诊卡</text>
|
||||
<view class="add-card-btn" @click="createNewCard">新建就诊卡</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -138,6 +192,26 @@ const loading = ref(true)
|
||||
const error = ref('')
|
||||
const defaultAvatar = '/static/user/user.png'
|
||||
|
||||
// 就诊卡选择相关
|
||||
const showCardModal = ref(false)
|
||||
const cardList = ref([])
|
||||
const loadingCards = ref(false)
|
||||
const consultType = ref('') // 'chat' 或 'video'
|
||||
|
||||
// 医生资质图片
|
||||
const qualificationImages = computed(() => {
|
||||
if (!doctor.value?.qualification_images) return []
|
||||
try {
|
||||
const images = typeof doctor.value.qualification_images === 'string'
|
||||
? JSON.parse(doctor.value.qualification_images)
|
||||
: doctor.value.qualification_images
|
||||
return Array.isArray(images) ? images : []
|
||||
} catch (e) {
|
||||
console.error('解析资质图片失败:', e)
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
const formatPatientCount = (count) => {
|
||||
if (count === undefined || count === null) return '1.2k+'
|
||||
if (count >= 1000) return (count / 1000).toFixed(1) + 'k+'
|
||||
@@ -149,6 +223,14 @@ const getExpertiseIcon = (icon) => {
|
||||
return map[icon] || '📋'
|
||||
}
|
||||
|
||||
// 预览资质图片
|
||||
const previewImage = (current) => {
|
||||
uni.previewImage({
|
||||
current: current,
|
||||
urls: qualificationImages.value
|
||||
})
|
||||
}
|
||||
|
||||
const loadDetail = async () => {
|
||||
if (!doctorId.value) return
|
||||
loading.value = true
|
||||
@@ -192,68 +274,136 @@ const viewAllReviews = () => {
|
||||
uni.showToast({ title: '查看全部评价', icon: 'none' })
|
||||
}
|
||||
|
||||
const goChat = async () => {
|
||||
// uni.navigateTo({
|
||||
// url: `/TUIKit/pages/chat/chat?doctorId=${doctorId.value}&doctorName=${encodeURIComponent(doctor.value?.name || '')}`
|
||||
// })
|
||||
console.log('dfafafa',uni.getStorageSync('userData'))
|
||||
if(!uni.getStorageSync('userData').diagnosis){
|
||||
uni.navigateTo({
|
||||
url: `/pages/Card/edit_card?add=1`
|
||||
});
|
||||
}else{
|
||||
await loginHandler(uni.getStorageSync('userData').diagnosis.patient_id)
|
||||
const doctor='doctor_'+doctorId.value
|
||||
// 跳转到聊天页面
|
||||
uni.navigateTo({
|
||||
url: `/TUIKit/pages/chat/chat?userID=${encodeURIComponent(userId)}&userSig=${encodeURIComponent(userSig)}&SDKAppID=${SDKAppID}&targetUserID=${doctor}`
|
||||
});
|
||||
// 显示就诊卡选择弹窗
|
||||
const showCardSelector = async (type) => {
|
||||
consultType.value = type
|
||||
showCardModal.value = true
|
||||
await loadCardList()
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closeCardModal = () => {
|
||||
showCardModal.value = false
|
||||
}
|
||||
|
||||
// 加载就诊卡列表
|
||||
const loadCardList = async () => {
|
||||
loadingCards.value = true
|
||||
try {
|
||||
const token = uni.getStorageSync('token')
|
||||
if (!token) {
|
||||
uni.showToast({ title: '请先登录', icon: 'none' })
|
||||
closeCardModal()
|
||||
return
|
||||
}
|
||||
|
||||
const userData = uni.getStorageSync('userData')
|
||||
const userId = userData?.id
|
||||
|
||||
if (!userId) {
|
||||
uni.showToast({ title: '用户信息不存在', icon: 'none' })
|
||||
closeCardModal()
|
||||
return
|
||||
}
|
||||
|
||||
const res = await proxy.apiUrl({
|
||||
url: '/api/tcm/getCardList',
|
||||
method: 'GET',
|
||||
data: { user_id: userId,patient_id:userId }
|
||||
}, false)
|
||||
|
||||
if (res && res.code === 1) {
|
||||
cardList.value = res.data || []
|
||||
} else {
|
||||
uni.showToast({ title: res?.msg || '获取失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载就诊卡列表失败:', e)
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
loadingCards.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const goVideo = () => {
|
||||
if(!uni.getStorageSync('userData').diagnosis){
|
||||
uni.navigateTo({
|
||||
url: `/pages/Card/edit_card?add=1`
|
||||
});
|
||||
}else{
|
||||
uni.navigateTo({
|
||||
url: `/pages/login/login?doctorId=${doctorId.value}&doctorName=${encodeURIComponent(doctor.value?.name || '')}`
|
||||
// 选择就诊卡
|
||||
const selectCard = async (card) => {
|
||||
closeCardModal()
|
||||
|
||||
// 更新本地存储的就诊卡信息
|
||||
const userData = uni.getStorageSync('userData') || {}
|
||||
userData.diagnosis = {
|
||||
patient_id: card.patient_id,
|
||||
id: card.id,
|
||||
patient_name: card.patient_name
|
||||
}
|
||||
uni.setStorageSync('userData', userData)
|
||||
|
||||
// 根据类型跳转
|
||||
if (consultType.value === 'chat') {
|
||||
await goChat(card)
|
||||
} else if (consultType.value === 'video') {
|
||||
await goChat(card)
|
||||
}
|
||||
}
|
||||
|
||||
// 新建就诊卡
|
||||
const createNewCard = () => {
|
||||
closeCardModal()
|
||||
uni.navigateTo({
|
||||
url: `/pages/Card/edit_card?add=1&returnUrl=/doctor/pages/doctor/doctor?id=${doctorId.value}`
|
||||
})
|
||||
}
|
||||
|
||||
const goChat = async (card) => {
|
||||
console.log('开始图文问诊', uni.getStorageSync('userData'))
|
||||
const userData = uni.getStorageSync('userData')
|
||||
if (!userData.diagnosis) {
|
||||
uni.showToast({ title: '请先选择就诊人', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
await loginHandler(userData.diagnosis.patient_id)
|
||||
const doctor = 'doctor_' + doctorId.value
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/TUIKit/pages/chat/chat?userID=${encodeURIComponent(userId.value)}&userSig=${encodeURIComponent(userSig.value)}&SDKAppID=${SDKAppID.value}&targetUserID=${doctor}&msg=0`
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const loginHandler = async (patient_id) => {
|
||||
console.log('获取签名开始:',patient_id);
|
||||
const res = await proxy.apiUrl({
|
||||
url: '/api/tcm/getPatientSignature',
|
||||
method: 'GET',
|
||||
data: { patient_id: patient_id },
|
||||
}, false)
|
||||
console.log('获取签名开始:', patient_id)
|
||||
const res = await proxy.apiUrl({
|
||||
url: '/api/tcm/getPatientSignature',
|
||||
method: 'GET',
|
||||
data: { patient_id: patient_id },
|
||||
}, false)
|
||||
|
||||
const {userId } = res.data;
|
||||
const { userSig, SDKAppID } = GenerateTestUserSig.genTestUserSig({
|
||||
userID:userId,
|
||||
});
|
||||
console.log('获取签名成功:',userSig);
|
||||
const { userId: patientUserId } = res.data
|
||||
const { userSig: patientUserSig, SDKAppID: patientSDKAppID } = GenerateTestUserSig.genTestUserSig({
|
||||
userID: patientUserId,
|
||||
})
|
||||
console.log('获取签名成功:', patientUserSig)
|
||||
|
||||
getApp().globalData.userID = userId;
|
||||
getApp().globalData.userSig = userSig;
|
||||
getApp().globalData.SDKAppID = SDKAppID;
|
||||
getApp().globalData.isIMInitialized = false; // 标记IM未初始化
|
||||
// 更新ref值
|
||||
userId.value = patientUserId
|
||||
userSig.value = patientUserSig
|
||||
SDKAppID.value = patientSDKAppID
|
||||
|
||||
getApp().globalData.userID = patientUserId
|
||||
getApp().globalData.userSig = patientUserSig
|
||||
getApp().globalData.SDKAppID = patientSDKAppID
|
||||
getApp().globalData.isIMInitialized = false
|
||||
|
||||
// 初始化视频通话
|
||||
await uni.CallManager.init({
|
||||
sdkAppID: SDKAppID,
|
||||
userID: userId,
|
||||
userSig: userSig,
|
||||
sdkAppID: patientSDKAppID,
|
||||
userID: patientUserId,
|
||||
userSig: patientUserSig,
|
||||
globalCallPagePath: "TUICallKit/src/Components/TUICallKit",
|
||||
});
|
||||
})
|
||||
|
||||
console.log('初始化完成,跳转到聊天页面');
|
||||
|
||||
|
||||
};
|
||||
console.log('初始化完成,跳转到聊天页面')
|
||||
}
|
||||
onMounted(() => {
|
||||
const pages = getCurrentPages()
|
||||
const current = pages[pages.length - 1]
|
||||
@@ -453,6 +603,24 @@ $outline-variant: #c1c9be;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.qualification-section {
|
||||
margin-bottom: 96rpx;
|
||||
}
|
||||
|
||||
.qualification-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.qualification-image {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
border-radius: 16rpx;
|
||||
background: $surface-high;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.expertise-section {
|
||||
margin-bottom: 96rpx;
|
||||
}
|
||||
@@ -647,4 +815,126 @@ $outline-variant: #c1c9be;
|
||||
.btn-icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 640rpx;
|
||||
max-height: 80vh;
|
||||
background: $surface-lowest;
|
||||
border-radius: 32rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 48rpx;
|
||||
border-bottom: 2rpx solid $outline-variant;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: $on-surface;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
font-size: 48rpx;
|
||||
color: $on-surface-variant;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-loading {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-list {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.card-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 16rpx;
|
||||
background: $surface-low;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
background: $surface-high;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
.card-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-name {
|
||||
display: block;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: $on-surface;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.card-detail {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: $on-surface-variant;
|
||||
}
|
||||
|
||||
.card-arrow {
|
||||
font-size: 48rpx;
|
||||
color: $on-surface-variant;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.modal-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 48rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: $on-surface-variant;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.add-card-btn {
|
||||
padding: 24rpx 64rpx;
|
||||
background: $primary;
|
||||
color: $on-primary;
|
||||
border-radius: 40rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
class="form-input-inline"
|
||||
type="digit"
|
||||
placeholder="请填写空腹血糖(数值)"
|
||||
required
|
||||
/>
|
||||
<text class="form-unit">mmol/L</text>
|
||||
</view>
|
||||
|
||||
@@ -460,7 +460,7 @@ onMounted(() => {
|
||||
onShareAppMessage((res) =>{
|
||||
return {
|
||||
title: '点击进入诊室',
|
||||
path: '/pages/order/monad/monad?id='+diagnosisId+'&share_user_id='+share_user_id,
|
||||
path: '/pages/order/monad/monad?id='+diagnosisId+'&share_user_id='+share_user_id+'&doctor_id='+doctorId.value,
|
||||
imageUrl: '/static/zs.jpg',
|
||||
desc: '点击进入诊室',
|
||||
success() {
|
||||
@@ -567,7 +567,7 @@ const loadDiagnosisDetail = async () => {
|
||||
const params = proxy.$parsePageParams(currentPage.options);
|
||||
dingdan_ok.value=false;
|
||||
diagnosisId = params.id;
|
||||
share_user_id = params.share_user_id;
|
||||
share_user_id = params.share_user;
|
||||
doctorId.value = params.doctor_id;
|
||||
console.log('进入onMounted:', params, '诊单ID:', diagnosisId, '医生ID:', doctorId);
|
||||
|
||||
@@ -585,7 +585,7 @@ const loadDiagnosisDetail = async () => {
|
||||
method: 'GET',
|
||||
data: {
|
||||
id: diagnosisId,
|
||||
user_id: params.user_id || 0,
|
||||
user_id: params.share_user_id || 0,
|
||||
share_user_id: params.share_user || 0
|
||||
},
|
||||
}, false); // 不显示加载中
|
||||
@@ -622,24 +622,29 @@ const closePatientModal = async () => {
|
||||
confirmBtnLoading.value = true;
|
||||
try {
|
||||
const app = getApp();
|
||||
const userID = app.globalData.userID;
|
||||
const userSig = app.globalData.userSig;
|
||||
const SDKAppID = app.globalData.SDKAppID;
|
||||
// const userID = app.globalData.userID;
|
||||
// const userSig = app.globalData.userSig;
|
||||
// const SDKAppID = app.globalData.SDKAppID;
|
||||
const doctor = 'doctor_' + doctorId.value;
|
||||
|
||||
confirmDiagnosis(diagnosisId);
|
||||
if(!diagnosisId.includes('patient')){
|
||||
diagnosisId='patient_'+diagnosisId
|
||||
}
|
||||
const { userSig, SDKAppID } = GenerateTestUserSig.genTestUserSig({
|
||||
userID:diagnosisId,
|
||||
});
|
||||
if (!doctorId.value) {
|
||||
uni.showToast({ title: '诊室号不存在,联系助理处理!', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!userID || !userSig) {
|
||||
await video();
|
||||
}
|
||||
console.log(userID, app.globalData, diagnosisId);
|
||||
|
||||
console.log(app.globalData, diagnosisId);
|
||||
|
||||
confirmDiagnosis();
|
||||
|
||||
|
||||
showPatientModal.value = false;
|
||||
uni.redirectTo({
|
||||
url: `/TUIKit/pages/chat/chat?userID=${encodeURIComponent(userID)}&userSig=${encodeURIComponent(userSig)}&SDKAppID=${SDKAppID}&targetUserID=${doctor}`
|
||||
url: `/TUIKit/pages/chat/chat?userID=${encodeURIComponent(diagnosisId)}&userSig=${encodeURIComponent(userSig)}&SDKAppID=${SDKAppID}&targetUserID=${doctor}&msg=1`
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -807,14 +812,12 @@ const handleConfirm = () => {
|
||||
};
|
||||
|
||||
// 提交确认诊单
|
||||
const confirmDiagnosis = async () => {
|
||||
const pages = getCurrentPages();
|
||||
const currentPage = pages[pages.length - 1];
|
||||
const params = proxy.$parsePageParams(currentPage.options);
|
||||
const confirmDiagnosis = async (diagnosisId) => {
|
||||
|
||||
console.log('确认诊单参数:', params);
|
||||
|
||||
if (!params.id) {
|
||||
console.log('确认诊单参数:', diagnosisId);
|
||||
|
||||
if (!diagnosisId) {
|
||||
uni.showToast({ title: '缺少诊单ID', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
@@ -822,15 +825,22 @@ const confirmDiagnosis = async () => {
|
||||
|
||||
|
||||
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: params.id,
|
||||
user_id: params.user_id || 0,
|
||||
share_user: params.share_user || 0
|
||||
id: diagnosisId,
|
||||
user_id: userID || 0,
|
||||
share_user: share_user_id || 0
|
||||
}
|
||||
}, false);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user