Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
1021 lines
29 KiB
Vue
1021 lines
29 KiB
Vue
<script setup>
|
||
import { computed, ref } from 'vue';
|
||
|
||
import { useVbenModal } from '@vben/common-ui';
|
||
import { useUserStore } from '@vben/stores';
|
||
|
||
import { Avatar, Button, Image } from 'ant-design-vue';
|
||
|
||
import { getChatMessageRegisterInfoApi } from '#/views/business/chat/api';
|
||
import { useChatStore } from '#/views/business/chat/stores/chat';
|
||
import { sendMessage } from '#/views/business/chat/utils/request';
|
||
import { getPrescriptionCheckStatusApi } from '#/views/business/order/prescription/api';
|
||
import { receptionApi } from '#/views/doctor/doctor-reception/api';
|
||
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
||
import RefusalOfTreatmentModal from '#/views/doctor/doctor-reception/components/RefusalOfTreatmentModal.vue';
|
||
|
||
import previewMedia from '../composables/useMediaPreview.ts';
|
||
import { useThemeStore } from '../stores/theme.ts';
|
||
import { useUserStore as chatUseUserStore } from '../stores/user';
|
||
import AudioMessage from './AudioMessage.vue';
|
||
|
||
const props = defineProps({
|
||
message: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
isSent: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
senderInfo: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
});
|
||
const emit = defineEmits(['show-user-profile']);
|
||
const chatUserStore = chatUseUserStore();
|
||
const userStore = useUserStore();
|
||
const chatStore = useChatStore();
|
||
const themeStore = useThemeStore();
|
||
|
||
const [RefusalOfTreatmentModals, RefusalOfTreatmentModalApi] = useVbenModal({
|
||
connectedComponent: RefusalOfTreatmentModal,
|
||
});
|
||
|
||
const avatarStyle = computed(() => ({
|
||
background: props.senderInfo.color,
|
||
}));
|
||
|
||
const statusInfo = ref({
|
||
status: 0,
|
||
text: '',
|
||
});
|
||
|
||
// 挂号操作状态
|
||
const registerOperating = ref(false);
|
||
|
||
// 添加处方状态映射
|
||
const prescriptionStatusMap = {
|
||
0: '待审核',
|
||
1: '已审核',
|
||
2: '未通过',
|
||
};
|
||
|
||
// 添加挂号状态映射 0待支付1已支付待接诊2接诊中3已结束4已取消5待评价6已评价7已拒诊
|
||
const registerStatusMap = {
|
||
0: '待支付',
|
||
1: '待接诊',
|
||
2: '接诊中',
|
||
3: '已结束',
|
||
4: '已取消',
|
||
5: '待评价',
|
||
6: '已评价',
|
||
7: '已拒诊',
|
||
};
|
||
|
||
// 添加支付状态映射
|
||
const payStatusMap = {
|
||
0: '未支付',
|
||
1: '已支付',
|
||
};
|
||
|
||
const checkPrescriptionStatus = (id) => {
|
||
if (!id) return;
|
||
getPrescriptionCheckStatusApi({ id }).then((res) => {
|
||
statusInfo.value.status = res.status;
|
||
statusInfo.value.text = prescriptionStatusMap[res.status];
|
||
return prescriptionStatusMap[res.status];
|
||
});
|
||
};
|
||
|
||
const getRegisterCardInfo = () => {
|
||
getChatMessageRegisterInfoApi({ id: props.message.content.id }).then(
|
||
(res) => {
|
||
// eslint-disable-next-line vue/no-mutating-props
|
||
props.message.content = res;
|
||
},
|
||
);
|
||
};
|
||
|
||
// 接诊操作
|
||
const acceptRegister = async (registerId, orderNo) => {
|
||
if (registerOperating.value) return;
|
||
|
||
registerOperating.value = true;
|
||
|
||
try {
|
||
// 模拟API调用
|
||
|
||
receptionApi(registerId).then(async (value) => {
|
||
// message.success('接诊成功');
|
||
// 更新消息状态
|
||
getRegisterCardInfo();
|
||
|
||
const messageObj = {
|
||
type: 'text',
|
||
content: '我已接诊,请简单说明您的情况~',
|
||
time: new Date().toLocaleTimeString().slice(0, 5),
|
||
senderId: `${chatUserStore.currentUser.id}`,
|
||
receiverId: chatStore.currentFriend.id,
|
||
isSent: true,
|
||
read: true,
|
||
duration: 0,
|
||
};
|
||
localStorage.setItem('currentRegisterId', reregisterId);
|
||
// chatStore.currentFriend?.register_id = reregisterId;
|
||
chatStore.addMessage(messageObj, chatUserStore.currentUser.id);
|
||
await sendMessage({
|
||
senderId: chatUserStore.currentUser.id,
|
||
receiverId: chatStore.currentFriend.id,
|
||
roomId: chatStore.currentFriend.room_id,
|
||
type: 'text',
|
||
content: '我已接诊,请简单说明您的情况~',
|
||
});
|
||
});
|
||
} catch (error) {
|
||
console.error('接诊失败:', error);
|
||
} finally {
|
||
registerOperating.value = false;
|
||
}
|
||
};
|
||
|
||
// 拒诊操作
|
||
const rejectRegister = async (registerId, orderNo) => {
|
||
if (registerOperating.value) return;
|
||
|
||
registerOperating.value = true;
|
||
|
||
RefusalOfTreatmentModalApi.setData({
|
||
values: {
|
||
register_id: registerId,
|
||
},
|
||
});
|
||
RefusalOfTreatmentModalApi.open();
|
||
registerOperating.value = false;
|
||
};
|
||
|
||
const bubbleClasses = computed(() => {
|
||
const baseClasses = ['message-bubble'];
|
||
|
||
if (props.isSent) {
|
||
baseClasses.push('sent');
|
||
} else {
|
||
baseClasses.push('received');
|
||
}
|
||
|
||
if (props.message.type === 'text') {
|
||
baseClasses.push('bubble-bg');
|
||
}
|
||
|
||
if (themeStore.isDarkMode) {
|
||
baseClasses.push('dark');
|
||
}
|
||
|
||
return baseClasses;
|
||
});
|
||
|
||
const handlePreviewMedia = (content, type) => {
|
||
previewMedia(content, type);
|
||
};
|
||
|
||
const showUserProfile = () => {
|
||
emit('show-user-profile', props.senderInfo);
|
||
};
|
||
|
||
const formatDuration = (seconds) => {
|
||
const mins = Math.floor(seconds / 60);
|
||
const secs = seconds % 60;
|
||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||
};
|
||
|
||
const formatTextWithLinks = (text) => {
|
||
console.log(text, props.message);
|
||
if (!text) return text; // 或 return text (但可能返回 undefined/null,根据需求调整)
|
||
const urlRegex = /(https?:\/\/\S+)/g;
|
||
return text.replaceAll(
|
||
urlRegex,
|
||
'<a href="$1" target="_blank" rel="noopener noreferrer" class="text-link">$1</a>',
|
||
);
|
||
};
|
||
|
||
const openUrl = (url) => {
|
||
window.open(url, '_blank', 'noopener,noreferrer');
|
||
};
|
||
|
||
const getCallStatusText = (status) => {
|
||
const statusMap = {
|
||
invite: '发起通话',
|
||
accepted: '通话已接通',
|
||
rejected: '通话被拒绝',
|
||
ended: '通话已结束',
|
||
missed: '未接通话',
|
||
};
|
||
return statusMap[status] || '通话消息';
|
||
};
|
||
|
||
const handleImageError = (event) => {
|
||
event.target.src = '/placeholder.svg?height=200&width=300';
|
||
};
|
||
|
||
const handleVideoError = (event) => {
|
||
console.error('视频加载失败:', event);
|
||
};
|
||
|
||
const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
||
connectedComponent: PrescriptionDetail,
|
||
});
|
||
|
||
const viewPrescription = (item) => {
|
||
PrescriptionDetailModalApi.setData({
|
||
values: item.id,
|
||
});
|
||
PrescriptionDetailModalApi.open();
|
||
};
|
||
|
||
// 格式化日期时间
|
||
const formatDateTime = (timestamp) => {
|
||
if (!timestamp) return '';
|
||
const date = new Date(timestamp * 1000);
|
||
return date.toLocaleString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
});
|
||
};
|
||
|
||
// 获取性别文本
|
||
const getSexText = (sex) => {
|
||
return sex === 1 ? '男' : '女';
|
||
};
|
||
|
||
if (props.message.type === 'register') {
|
||
getRegisterCardInfo();
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div
|
||
:class="isSent ? 'flex-row-reverse' : 'flex-row'"
|
||
class="mb-4 flex items-start gap-3"
|
||
>
|
||
<RefusalOfTreatmentModals />
|
||
<PrescriptionDetailModal />
|
||
<!-- 头像 -->
|
||
<div
|
||
v-if="isSent"
|
||
:style="avatarStyle"
|
||
class="cursor-pointer rounded-full"
|
||
@click="showUserProfile"
|
||
>
|
||
<Avatar
|
||
v-if="userStore.userInfo?.avatar"
|
||
:size="48"
|
||
:src="userStore.userInfo?.avatar"
|
||
/>
|
||
<Avatar v-else :size="48">
|
||
{{ userStore.userInfo?.nick_name.charAt(0) }}
|
||
</Avatar>
|
||
</div>
|
||
<div
|
||
v-else
|
||
:style="avatarStyle"
|
||
class="cursor-pointer rounded-full"
|
||
@click="showUserProfile"
|
||
>
|
||
<Avatar v-if="senderInfo.avatar" :size="48" :src="senderInfo.avatar" />
|
||
<Avatar v-else :size="48">{{ senderInfo.nick_name.charAt(0) }}</Avatar>
|
||
</div>
|
||
|
||
<!-- 消息内容区域 -->
|
||
<div
|
||
:class="isSent ? 'items-end' : 'items-start'"
|
||
class="flex max-w-xs flex-col md:max-w-md lg:max-w-lg xl:max-w-xl"
|
||
>
|
||
<!-- 发送者名称 -->
|
||
<div
|
||
v-if="isSent"
|
||
class="mb-1 px-2 text-xs text-gray-500 dark:text-gray-400"
|
||
>
|
||
{{ userStore.userInfo?.nick_name }}
|
||
</div>
|
||
<div v-else class="mb-1 px-2 text-xs text-gray-500 dark:text-gray-400">
|
||
{{ senderInfo.nick_name }}
|
||
</div>
|
||
|
||
<!-- 消息气泡 -->
|
||
<div :class="bubbleClasses" class="relative rounded-2xl p-4 shadow-sm">
|
||
<!-- 图片消息 -->
|
||
<div
|
||
v-if="message.type === 'image'"
|
||
class="relative cursor-pointer"
|
||
@click="handlePreviewMedia(message.content, 'image')"
|
||
>
|
||
<Image
|
||
:src="message.content"
|
||
alt="图片消息"
|
||
class="max-w-full rounded-lg"
|
||
@error="handleImageError"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 视频消息 -->
|
||
<div
|
||
v-else-if="message.type === 'video'"
|
||
class="video-message relative cursor-pointer"
|
||
@click="handlePreviewMedia(message.content, 'video')"
|
||
>
|
||
<video
|
||
:src="message.content"
|
||
class="video-thumbnail max-w-full rounded-lg"
|
||
preload="metadata"
|
||
@error="handleVideoError"
|
||
></video>
|
||
<div class="video-overlay">
|
||
<div class="play-button">
|
||
<i class="fas fa-play"></i>
|
||
</div>
|
||
<div v-if="message.duration" class="video-duration">
|
||
{{ formatDuration(message.duration) }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- URL链接消息 -->
|
||
<div v-else-if="message.type === 'url'" class="url-message">
|
||
<div class="url-preview" @click="openUrl(message.url)">
|
||
<div class="url-favicon">
|
||
<img
|
||
v-if="message.favicon"
|
||
:alt="message.domain"
|
||
:src="message.favicon"
|
||
/>
|
||
<i v-else class="fas fa-link"></i>
|
||
</div>
|
||
<div class="url-content">
|
||
<div class="url-title">{{ message.title || message.url }}</div>
|
||
<div v-if="message.description" class="url-description">
|
||
{{ message.description }}
|
||
</div>
|
||
<div class="url-domain">{{ message.domain }}</div>
|
||
</div>
|
||
<div v-if="message.thumbnail" class="url-thumbnail">
|
||
<img :alt="message.title" :src="message.thumbnail" />
|
||
</div>
|
||
</div>
|
||
<div v-if="message.text" class="url-text">
|
||
<Button type="link">{{ message.text }}</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 音频消息 -->
|
||
<AudioMessage
|
||
v-else-if="message.type === 'audio'"
|
||
:is-sent="isSent"
|
||
:message="message"
|
||
/>
|
||
|
||
<!-- 挂号卡片消息 -->
|
||
<div v-else-if="message.type === 'register'" class="register-card">
|
||
<div class="flex items-center gap-3">
|
||
<div
|
||
class="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-green-100 dark:bg-green-900"
|
||
>
|
||
<i
|
||
class="fas fa-calendar-check text-xl text-green-500 dark:text-green-300"
|
||
></i>
|
||
</div>
|
||
|
||
<div class="min-w-0 flex-1">
|
||
<div class="flex items-center justify-between">
|
||
<div class="font-semibold text-gray-800 dark:text-gray-100">
|
||
挂号信息
|
||
</div>
|
||
<div
|
||
:class="{
|
||
'bg-yellow-100 text-yellow-800':
|
||
message.content.status === 0,
|
||
'bg-blue-100 text-blue-800': message.content.status === 1,
|
||
'bg-green-100 text-green-800': message.content.status === 2,
|
||
'bg-red-100 text-red-800': message.content.status === 3,
|
||
'bg-gray-100 text-gray-800': message.content.status === 4,
|
||
'bg-purple-100 text-purple-800':
|
||
message.content.status === 5,
|
||
'bg-indigo-100 text-indigo-800':
|
||
message.content.status === 6,
|
||
'bg-pink-100 text-pink-800': message.content.status === 7,
|
||
}"
|
||
class="rounded-full px-2 py-1 text-xs font-medium"
|
||
>
|
||
{{ registerStatusMap[message.content.status] || '未知状态' }}
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="mt-1 flex flex-wrap gap-2 text-xs text-gray-600 dark:text-gray-300"
|
||
>
|
||
<div class="flex items-center gap-1">
|
||
<i class="fas fa-hashtag text-xs"></i>
|
||
<span class="max-w-[120px] truncate">{{
|
||
message.content.order_no
|
||
}}</span>
|
||
</div>
|
||
|
||
<div class="flex items-center gap-1">
|
||
<i class="fas fa-list-ol text-xs"></i>
|
||
<span>第{{ message.content.order_number }}号</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 患者信息 -->
|
||
<div class="mt-3 rounded-lg bg-gray-50 p-3 dark:bg-gray-800">
|
||
<div class="mb-2 flex items-center justify-between">
|
||
<div class="font-medium text-gray-700 dark:text-gray-200">
|
||
患者信息
|
||
</div>
|
||
<div
|
||
:class="{
|
||
'bg-red-100 text-red-800': message.content.is_pay === 0,
|
||
'bg-green-100 text-green-800': message.content.is_pay === 1,
|
||
}"
|
||
class="rounded-full px-2 py-1 text-xs font-medium"
|
||
>
|
||
{{ payStatusMap[message.content.is_pay] || '未知' }}
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="grid grid-cols-2 gap-2 text-sm text-gray-600 dark:text-gray-300"
|
||
>
|
||
<div class="flex items-center gap-1">
|
||
<i class="fas fa-user text-xs"></i>
|
||
<span>{{ message.content.user_patient?.name || '未知' }}</span>
|
||
</div>
|
||
<div class="flex items-center gap-1">
|
||
<i class="fas fa-birthday-cake text-xs"></i>
|
||
<span>{{ message.content.user_patient?.age || 0 }}岁</span>
|
||
</div>
|
||
<div class="flex items-center gap-1">
|
||
<i class="fas fa-venus-mars text-xs"></i>
|
||
<span>{{ getSexText(message.content.user_patient?.sex) }}</span>
|
||
</div>
|
||
<div class="flex items-center gap-1">
|
||
<i class="fas fa-phone text-xs"></i>
|
||
<span>{{
|
||
message.content.user_patient?.mobile ? '已绑定' : '未绑定'
|
||
}}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 操作按钮区域 -->
|
||
<div class="mt-3 border-t border-gray-200 pt-3 dark:border-gray-600">
|
||
<div class="mb-2 flex items-center justify-between">
|
||
<div class="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||
挂号费用:
|
||
<span class="text-green-500 dark:text-green-400">¥{{ message.content.price }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 接诊/拒诊按钮 - 只在待就诊状态显示 -->
|
||
<div v-if="message.content.status === 1" class="mt-2 flex gap-2">
|
||
<Button
|
||
:loading="registerOperating"
|
||
class="flex-1"
|
||
size="small"
|
||
type="primary"
|
||
@click="
|
||
acceptRegister(message.content.id, message.content.order_no)
|
||
"
|
||
>
|
||
<i class="fas fa-check mr-1"></i>
|
||
接诊
|
||
</Button>
|
||
<Button
|
||
:loading="registerOperating"
|
||
class="flex-1"
|
||
danger
|
||
size="small"
|
||
@click="
|
||
rejectRegister(message.content.id, message.content.order_no)
|
||
"
|
||
>
|
||
<i class="fas fa-times mr-1"></i>
|
||
拒诊
|
||
</Button>
|
||
</div>
|
||
|
||
<!-- 查看详情按钮 -->
|
||
<!-- <div class="flex items-center justify-center mt-2">-->
|
||
<!-- <div class="flex items-center text-blue-500 dark:text-blue-300 hover:text-blue-600 dark:hover:text-blue-200 transition-colors cursor-pointer">-->
|
||
<!-- <span class="text-sm font-medium">查看详情</span>-->
|
||
<!-- <i class="fas fa-chevron-right ml-1 text-xs"></i>-->
|
||
<!-- </div>-->
|
||
<!-- </div>-->
|
||
</div>
|
||
|
||
<!-- 时间信息 -->
|
||
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||
创建时间: {{ message.content.created_at }}
|
||
<span v-if="message.content.pay_time" class="ml-2">
|
||
支付时间: {{ formatDateTime(message.content.pay_time) }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 处方卡片消息 -->
|
||
<div
|
||
v-else-if="message.type === 'prescription'"
|
||
class="prescription-card"
|
||
@click="viewPrescription(message.content)"
|
||
>
|
||
<div class="flex items-center gap-3">
|
||
<div
|
||
class="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900"
|
||
>
|
||
<i
|
||
class="fas fa-file-prescription text-xl text-blue-500 dark:text-blue-300"
|
||
></i>
|
||
</div>
|
||
|
||
<div class="min-w-0 flex-1">
|
||
<div class="flex items-center justify-between">
|
||
<div class="font-semibold text-gray-800 dark:text-gray-100">
|
||
电子处方
|
||
</div>
|
||
<div
|
||
:class="{
|
||
'bg-yellow-100 text-yellow-800': statusInfo.status === 0,
|
||
'bg-green-100 text-green-800': statusInfo.status === 1,
|
||
'bg-red-100 text-red-800': statusInfo.status === 2,
|
||
'bg-blue-100 text-blue-800': statusInfo.status === 3,
|
||
'bg-gray-100 text-gray-800': statusInfo.status === 4,
|
||
}"
|
||
class="rounded-full px-2 py-1 text-xs font-medium"
|
||
>
|
||
{{
|
||
checkPrescriptionStatus(message.content.id) ||
|
||
statusInfo.text ||
|
||
'加载中'
|
||
}}
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="mt-1 flex flex-wrap gap-2 text-xs text-gray-600 dark:text-gray-300"
|
||
>
|
||
<div class="flex items-center gap-1">
|
||
<i class="fas fa-hashtag text-xs"></i>
|
||
<span class="max-w-[100px] truncate">{{
|
||
message.content.order_no
|
||
}}</span>
|
||
</div>
|
||
|
||
<div class="flex items-center gap-1">
|
||
<i class="fas fa-truck text-xs"></i>
|
||
<span>{{ message.content.express_name }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="mt-3 flex items-center justify-between border-t border-gray-200 pt-2 dark:border-gray-600"
|
||
>
|
||
<div class="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||
支付金额:
|
||
<span class="text-red-500 dark:text-red-400">
|
||
¥{{ message.content.total_pay_price }}
|
||
</span>
|
||
</div>
|
||
|
||
<div
|
||
class="flex items-center text-blue-500 transition-colors hover:text-blue-600 dark:text-blue-300 dark:hover:text-blue-200"
|
||
>
|
||
<span class="text-sm font-medium">查看处方</span>
|
||
<i class="fas fa-chevron-right ml-1 text-xs"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 在线复诊|购药信息 -->
|
||
<div
|
||
v-else-if="message.type === 'follow-up-visit'"
|
||
class="group transition-all duration-200"
|
||
>
|
||
<!-- 卡片容器 -->
|
||
<div
|
||
:class="{
|
||
'border-green-200': message.content.hasVisited === '1', // 已到访状态
|
||
'border-yellow-200': message.content.hasVisited === '0', // 未到访状态
|
||
}"
|
||
class="rounded-lg border p-4 shadow-sm"
|
||
>
|
||
<!-- 标题区域 -->
|
||
<div class="mb-3 flex items-center justify-between">
|
||
<h3 class="flex items-center text-lg font-semibold">
|
||
<svg
|
||
class="mr-2 h-4 w-4 text-green-500"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
viewBox="0 0 24 24"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
>
|
||
<path
|
||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
stroke-width="2"
|
||
/>
|
||
</svg>
|
||
{{ message.content.drug_name }} 复诊信息
|
||
</h3>
|
||
<span
|
||
:class="{
|
||
'text-green-800': message.content.hasVisited === '1',
|
||
'bg-yellow-100 text-yellow-800':
|
||
message.content.hasVisited === '0',
|
||
}"
|
||
class="rounded-full px-2 py-1 text-xs"
|
||
>
|
||
{{ message.content.hasVisited === '1' ? '就诊过' : '未就诊过' }}
|
||
</span>
|
||
</div>
|
||
|
||
<!-- 症状信息 -->
|
||
<div class="space-y-2">
|
||
<p class="flex items-start text-sm">
|
||
<span class="w-20">症状描述:</span>
|
||
<span class="flex-1">{{ message.content.illnessInfo }}</span>
|
||
</p>
|
||
|
||
<!-- 用药状态 -->
|
||
<p
|
||
:class="{
|
||
'font-medium text-blue-600':
|
||
message.content.hasUsedDrug === '1',
|
||
'text-gray-500': message.content.hasUsedDrug === '0',
|
||
}"
|
||
class="text-sm"
|
||
>
|
||
<span class="w-20 text-gray-500">用药情况:</span>
|
||
{{
|
||
message.content.hasUsedDrug === '1' ? '使用过' : '未使用过'
|
||
}}
|
||
</p>
|
||
|
||
<!-- 医生信息 -->
|
||
<p
|
||
class="mt-3 border-t border-gray-100 pt-3 text-sm text-gray-500"
|
||
>
|
||
<span class="w-20 text-gray-400">主诊医生:</span>
|
||
ID: {{ message.content.doctor_id }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 通话消息 -->
|
||
<div
|
||
v-else-if="
|
||
message.type === 'video-call' || message.type === 'voice-call'
|
||
"
|
||
class="call-message"
|
||
>
|
||
<div class="call-info">
|
||
<i
|
||
:class="message.type === 'video-call' ? 'fa-video' : 'fa-phone'"
|
||
class="fas"
|
||
></i>
|
||
<span>{{ getCallStatusText(message.callStatus) }}</span>
|
||
</div>
|
||
<div v-if="message.duration" class="call-duration">
|
||
通话时长: {{ formatDuration(message.duration) }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 文本消息 -->
|
||
<div v-else class="whitespace-pre-wrap text-base leading-relaxed">
|
||
<span v-html="formatTextWithLinks(message.content)"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 消息时间和状态 -->
|
||
<div
|
||
class="mt-1 flex items-center gap-1 px-2 text-xs text-gray-400 dark:text-gray-500"
|
||
>
|
||
<span>{{ message.time }}</span>
|
||
<i
|
||
v-if="isSent && message.read"
|
||
class="fas fa-check-double text-green-500"
|
||
></i>
|
||
<i
|
||
v-else-if="isSent && !message.read"
|
||
class="fas fa-clock text-gray-400"
|
||
></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.message-bubble {
|
||
position: relative;
|
||
max-width: 100%;
|
||
word-wrap: break-word;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.message-bubble.sent {
|
||
color: white;
|
||
border-bottom-right-radius: 4px;
|
||
}
|
||
|
||
.bubble-bg {
|
||
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
||
}
|
||
.bubble-bg.dark {
|
||
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
||
}
|
||
|
||
.message-bubble.received.bubble-bg {
|
||
background: white;
|
||
color: #1a202c;
|
||
border: 1px solid #e2e8f0;
|
||
border-bottom-left-radius: 4px;
|
||
}
|
||
|
||
.message-bubble.received.dark.bubble-bg {
|
||
background: #374151;
|
||
color: #f7fafc;
|
||
border-color: #4b5563;
|
||
}
|
||
|
||
.video-message {
|
||
position: relative;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
max-width: 300px;
|
||
}
|
||
|
||
.video-thumbnail {
|
||
width: 100%;
|
||
height: auto;
|
||
max-height: 200px;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
|
||
.video-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: linear-gradient(
|
||
to bottom,
|
||
transparent 0%,
|
||
rgba(0, 0, 0, 0.3) 100%
|
||
);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.video-message:hover .video-overlay {
|
||
opacity: 1;
|
||
}
|
||
|
||
.play-button {
|
||
width: 60px;
|
||
height: 60px;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: white;
|
||
font-size: 24px;
|
||
backdrop-filter: blur(10px);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.play-button:hover {
|
||
background: rgba(0, 0, 0, 0.8);
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.video-duration {
|
||
position: absolute;
|
||
bottom: 8px;
|
||
right: 8px;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
color: white;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.call-message {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding: 4px 0;
|
||
}
|
||
|
||
.call-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.call-info i {
|
||
width: 16px;
|
||
text-align: center;
|
||
}
|
||
|
||
.call-duration {
|
||
font-size: 12px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.url-message {
|
||
max-width: 400px;
|
||
}
|
||
|
||
.url-preview {
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
background: white;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
padding: 12px;
|
||
gap: 12px;
|
||
}
|
||
|
||
.url-preview:hover {
|
||
border-color: #4361ee;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 20px rgba(67, 97, 238, 0.15);
|
||
}
|
||
|
||
.message-bubble.sent .url-preview {
|
||
border-color: rgba(255, 255, 255, 0.3);
|
||
background: rgba(255, 255, 255, 0.1);
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.message-bubble.received.dark .url-preview {
|
||
border-color: #4b5563;
|
||
background: #4b5563;
|
||
}
|
||
|
||
.url-favicon {
|
||
width: 20px;
|
||
height: 20px;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.url-favicon img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.url-content {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.url-title {
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
margin-bottom: 4px;
|
||
color: #1a202c;
|
||
line-height: 1.4;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.message-bubble.sent .url-title {
|
||
color: white;
|
||
}
|
||
|
||
.message-bubble.received.dark .url-title {
|
||
color: #f7fafc;
|
||
}
|
||
|
||
.url-description {
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
margin-bottom: 4px;
|
||
line-height: 1.3;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.message-bubble.sent .url-description {
|
||
color: rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.message-bubble.received.dark .url-description {
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.url-domain {
|
||
font-size: 11px;
|
||
color: #94a3b8;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.message-bubble.sent .url-domain {
|
||
color: rgba(255, 255, 255, 0.7);
|
||
}
|
||
|
||
.message-bubble.received.dark .url-domain {
|
||
color: #6b7280;
|
||
}
|
||
|
||
.url-thumbnail {
|
||
width: 60px;
|
||
height: 60px;
|
||
flex-shrink: 0;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.url-thumbnail img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.url-text {
|
||
margin-top: 8px;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
:deep(.text-link) {
|
||
color: #4361ee;
|
||
text-decoration: underline;
|
||
transition: color 0.2s ease;
|
||
}
|
||
|
||
:deep(.text-link:hover) {
|
||
color: #3f37c9;
|
||
}
|
||
|
||
.message-bubble.sent :deep(.text-link) {
|
||
color: rgba(255, 255, 255, 0.9);
|
||
}
|
||
|
||
.message-bubble.sent :deep(.text-link:hover) {
|
||
color: white;
|
||
}
|
||
|
||
/* 挂号卡片样式 */
|
||
.register-card {
|
||
@apply w-full max-w-xs cursor-pointer rounded-xl bg-white p-4 shadow-sm transition-all duration-300 md:max-w-sm dark:bg-gray-700;
|
||
@apply border border-gray-200 hover:border-green-300 hover:shadow-md dark:border-gray-600 dark:hover:border-green-500;
|
||
}
|
||
|
||
.message-bubble.sent .register-card {
|
||
@apply border-green-200/50 bg-green-50/30 dark:border-green-700/70 dark:bg-green-900/30;
|
||
}
|
||
|
||
.register-card:hover {
|
||
@apply -translate-y-0.5 transform;
|
||
}
|
||
|
||
/* 处方卡片样式 - 使用TailwindCSS类替代 */
|
||
.prescription-card {
|
||
@apply w-full max-w-xs cursor-pointer rounded-xl bg-white p-4 shadow-sm transition-all duration-300 md:max-w-sm dark:bg-gray-700;
|
||
@apply border border-gray-200 hover:border-blue-300 hover:shadow-md dark:border-gray-600 dark:hover:border-blue-500;
|
||
}
|
||
|
||
.message-bubble.sent .prescription-card {
|
||
@apply border-blue-200/50 bg-blue-50/30 dark:border-blue-700/70 dark:bg-blue-900/30;
|
||
}
|
||
|
||
.prescription-card:hover {
|
||
@apply -translate-y-0.5 transform;
|
||
}
|
||
</style>
|