2025-07-25 13:02:19 +08:00
|
|
|
<script setup>
|
2025-08-02 16:57:57 +08:00
|
|
|
import {computed, ref} from 'vue';
|
2025-07-25 13:02:19 +08:00
|
|
|
|
2025-08-02 16:57:57 +08:00
|
|
|
import { useVbenModal } from '@vben/common-ui';
|
2025-07-25 13:02:19 +08:00
|
|
|
import { useUserStore } from '@vben/stores';
|
|
|
|
|
|
2025-08-02 16:57:57 +08:00
|
|
|
import {Avatar, Button, Image} from 'ant-design-vue';
|
|
|
|
|
|
|
|
|
|
import {getPrescriptionCheckStatusApi} from "#/views/business/order/prescription/api";
|
|
|
|
|
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
2025-07-25 13:02:19 +08:00
|
|
|
|
|
|
|
|
import previewMedia from '../composables/useMediaPreview.ts';
|
|
|
|
|
import {useThemeStore} from '../stores/theme.ts';
|
|
|
|
|
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 userStore = useUserStore();
|
|
|
|
|
|
|
|
|
|
const themeStore = useThemeStore();
|
|
|
|
|
|
|
|
|
|
const avatarStyle = computed(() => ({
|
|
|
|
|
background: props.senderInfo.color,
|
|
|
|
|
}));
|
|
|
|
|
|
2025-08-02 16:57:57 +08:00
|
|
|
const statusInfo = ref({
|
|
|
|
|
status: 0,
|
|
|
|
|
text: '',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 添加处方状态映射
|
|
|
|
|
const prescriptionStatusMap = {
|
|
|
|
|
0: '待审核',
|
|
|
|
|
1: '已审核',
|
|
|
|
|
2: '未通过',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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]
|
|
|
|
|
})
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-25 13:02:19 +08:00
|
|
|
const bubbleClasses = computed(() => {
|
|
|
|
|
const baseClasses = ['message-bubble'];
|
|
|
|
|
|
|
|
|
|
if (props.isSent) {
|
|
|
|
|
baseClasses.push('sent');
|
|
|
|
|
} else {
|
|
|
|
|
baseClasses.push('received');
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 16:03:07 +08:00
|
|
|
if (props.message.type === 'text') {
|
|
|
|
|
baseClasses.push('bubble-bg');
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-25 13:02:19 +08:00
|
|
|
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) => {
|
|
|
|
|
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);
|
|
|
|
|
};
|
2025-07-31 16:03:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
|
|
|
|
connectedComponent: PrescriptionDetail,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const viewPrescription = (item) => {
|
|
|
|
|
PrescriptionDetailModalApi.setData({
|
|
|
|
|
values: item.id,
|
|
|
|
|
})
|
|
|
|
|
PrescriptionDetailModalApi.open();
|
|
|
|
|
}
|
2025-08-02 16:57:57 +08:00
|
|
|
|
2025-07-25 13:02:19 +08:00
|
|
|
</script>
|
|
|
|
|
|
2025-07-19 16:47:52 +08:00
|
|
|
<template>
|
2025-07-25 13:02:19 +08:00
|
|
|
<div
|
|
|
|
|
:class="isSent ? 'flex-row-reverse' : 'flex-row'"
|
|
|
|
|
class="mb-4 flex items-start gap-3"
|
|
|
|
|
>
|
2025-07-31 16:03:07 +08:00
|
|
|
<PrescriptionDetailModal />
|
2025-07-19 16:47:52 +08:00
|
|
|
<!-- 头像 -->
|
2025-07-25 13:02:19 +08:00
|
|
|
<!-- <div-->
|
|
|
|
|
<!-- :style="avatarStyle"-->
|
|
|
|
|
<!-- class="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold text-sm flex-shrink-0 cursor-pointer"-->
|
|
|
|
|
<!-- @click="showUserProfile"-->
|
|
|
|
|
<!-- >-->
|
2025-07-19 16:47:52 +08:00
|
|
|
<div
|
2025-07-25 13:02:19 +08:00
|
|
|
v-if="isSent"
|
2025-07-19 16:47:52 +08:00
|
|
|
:style="avatarStyle"
|
2025-07-25 13:02:19 +08:00
|
|
|
class="rounded-full cursor-pointer"
|
2025-07-19 16:47:52 +08:00
|
|
|
@click="showUserProfile"
|
|
|
|
|
>
|
2025-07-25 13:02:19 +08:00
|
|
|
<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="rounded-full cursor-pointer"
|
|
|
|
|
@click="showUserProfile"
|
|
|
|
|
>
|
|
|
|
|
<Avatar v-if="senderInfo.avatar" :size="48" :src="senderInfo.avatar" />
|
|
|
|
|
<Avatar v-else :size="48">{{ senderInfo.nick_name.charAt(0) }}</Avatar>
|
2025-07-19 16:47:52 +08:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 消息内容区域 -->
|
2025-07-25 13:02:19 +08:00
|
|
|
<div
|
|
|
|
|
:class="isSent ? 'items-end' : 'items-start'"
|
|
|
|
|
class="flex flex-col max-w-xs md:max-w-md lg:max-w-lg xl:max-w-xl"
|
|
|
|
|
>
|
2025-07-19 16:47:52 +08:00
|
|
|
<!-- 发送者名称 -->
|
2025-07-25 13:02:19 +08:00
|
|
|
<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">
|
2025-07-19 16:47:52 +08:00
|
|
|
{{ senderInfo.nick_name }}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 消息气泡 -->
|
2025-07-25 13:02:19 +08:00
|
|
|
<div :class="bubbleClasses" class="relative rounded-2xl p-4 shadow-sm">
|
2025-07-19 16:47:52 +08:00
|
|
|
<!-- 图片消息 -->
|
|
|
|
|
<div
|
|
|
|
|
v-if="message.type === 'image'"
|
2025-07-25 13:02:19 +08:00
|
|
|
class="relative cursor-pointer"
|
2025-07-19 16:47:52 +08:00
|
|
|
@click="handlePreviewMedia(message.content, 'image')"
|
|
|
|
|
>
|
2025-07-25 13:02:19 +08:00
|
|
|
<Image
|
2025-07-19 16:47:52 +08:00
|
|
|
:src="message.content"
|
2025-07-25 13:02:19 +08:00
|
|
|
alt="图片消息"
|
2025-07-19 16:47:52 +08:00
|
|
|
class="rounded-lg max-w-full"
|
|
|
|
|
@error="handleImageError"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 视频消息 -->
|
|
|
|
|
<div
|
|
|
|
|
v-else-if="message.type === 'video'"
|
2025-07-25 13:02:19 +08:00
|
|
|
class="video-message relative cursor-pointer"
|
2025-07-19 16:47:52 +08:00
|
|
|
@click="handlePreviewMedia(message.content, 'video')"
|
|
|
|
|
>
|
|
|
|
|
<video
|
|
|
|
|
:src="message.content"
|
|
|
|
|
class="rounded-lg max-w-full video-thumbnail"
|
|
|
|
|
preload="metadata"
|
|
|
|
|
@error="handleVideoError"
|
2025-07-25 13:02:19 +08:00
|
|
|
></video>
|
2025-07-19 16:47:52 +08:00
|
|
|
<div class="video-overlay">
|
|
|
|
|
<div class="play-button">
|
|
|
|
|
<i class="fas fa-play"></i>
|
|
|
|
|
</div>
|
2025-07-25 13:02:19 +08:00
|
|
|
<div v-if="message.duration" class="video-duration">
|
2025-07-19 16:47:52 +08:00
|
|
|
{{ 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">
|
2025-07-25 13:02:19 +08:00
|
|
|
<img
|
|
|
|
|
v-if="message.favicon"
|
|
|
|
|
:alt="message.domain"
|
|
|
|
|
:src="message.favicon"
|
|
|
|
|
/>
|
|
|
|
|
<i v-else class="fas fa-link"></i>
|
2025-07-19 16:47:52 +08:00
|
|
|
</div>
|
|
|
|
|
<div class="url-content">
|
|
|
|
|
<div class="url-title">{{ message.title || message.url }}</div>
|
2025-07-25 13:02:19 +08:00
|
|
|
<div v-if="message.description" class="url-description">
|
2025-07-19 16:47:52 +08:00
|
|
|
{{ message.description }}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="url-domain">{{ message.domain }}</div>
|
|
|
|
|
</div>
|
2025-07-25 13:02:19 +08:00
|
|
|
<div v-if="message.thumbnail" class="url-thumbnail">
|
|
|
|
|
<img :alt="message.title" :src="message.thumbnail" />
|
2025-07-19 16:47:52 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-07-25 13:02:19 +08:00
|
|
|
<div v-if="message.text" class="url-text">
|
2025-08-02 16:57:57 +08:00
|
|
|
<Button type="link">{{ message.text }}</Button>
|
2025-07-19 16:47:52 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 音频消息 -->
|
2025-07-25 13:02:19 +08:00
|
|
|
<AudioMessage
|
|
|
|
|
v-else-if="message.type === 'audio'"
|
|
|
|
|
:is-sent="isSent"
|
|
|
|
|
:message="message"
|
|
|
|
|
/>
|
2025-07-19 16:47:52 +08:00
|
|
|
|
2025-07-31 16:03:07 +08:00
|
|
|
<!-- 处方卡片消息 -->
|
|
|
|
|
<!-- 处方卡片消息 -->
|
|
|
|
|
<div
|
|
|
|
|
v-else-if="message.type === 'prescription'"
|
|
|
|
|
class="prescription-card"
|
|
|
|
|
@click="viewPrescription(message.content)"
|
|
|
|
|
>
|
|
|
|
|
<div class="flex items-center gap-3">
|
|
|
|
|
<div class="w-12 h-12 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center flex-shrink-0">
|
|
|
|
|
<i class="fas fa-file-prescription text-blue-500 dark:text-blue-300 text-xl"></i>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="flex-1 min-w-0">
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
<div class="font-semibold text-gray-800 dark:text-gray-100">电子处方</div>
|
|
|
|
|
<div
|
|
|
|
|
:class="{
|
2025-08-02 16:57:57 +08:00
|
|
|
'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
|
2025-07-31 16:03:07 +08:00
|
|
|
}"
|
2025-08-02 16:57:57 +08:00
|
|
|
class="text-xs px-2 py-1 rounded-full font-medium"
|
2025-07-31 16:03:07 +08:00
|
|
|
>
|
2025-08-02 16:57:57 +08:00
|
|
|
{{ checkPrescriptionStatus(message.content.id) || statusInfo.text || '加载中' }}
|
|
|
|
|
<!-- {{ statusInfo.text || '加载中' }}-->
|
2025-07-31 16:03:07 +08:00
|
|
|
</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="truncate max-w-[100px]">{{ 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 pt-2 border-t border-gray-200 dark:border-gray-600 flex items-center justify-between">
|
|
|
|
|
<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 dark:text-blue-300 hover:text-blue-600 dark:hover:text-blue-200 transition-colors">
|
|
|
|
|
<span class="text-sm font-medium">查看处方</span>
|
|
|
|
|
<i class="fas fa-chevron-right ml-1 text-xs"></i>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-07-19 16:47:52 +08:00
|
|
|
<!-- 通话消息 -->
|
2025-07-25 13:02:19 +08:00
|
|
|
<div
|
|
|
|
|
v-else-if="message.type === 'video-call' || message.type === 'voice-call'"
|
|
|
|
|
"
|
|
|
|
|
class="call-message"
|
|
|
|
|
>
|
2025-07-19 16:47:52 +08:00
|
|
|
<div class="call-info">
|
2025-07-25 13:02:19 +08:00
|
|
|
<i
|
|
|
|
|
:class="message.type === 'video-call' ? 'fa-video' : 'fa-phone'"
|
|
|
|
|
class="fas"
|
|
|
|
|
></i>
|
2025-07-19 16:47:52 +08:00
|
|
|
<span>{{ getCallStatusText(message.callStatus) }}</span>
|
|
|
|
|
</div>
|
2025-07-25 13:02:19 +08:00
|
|
|
<div v-if="message.duration" class="call-duration">
|
2025-07-19 16:47:52 +08:00
|
|
|
通话时长: {{ formatDuration(message.duration) }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 文本消息 -->
|
2025-07-25 13:02:19 +08:00
|
|
|
<div v-else class="whitespace-pre-wrap text-base leading-relaxed">
|
2025-07-19 16:47:52 +08:00
|
|
|
<span v-html="formatTextWithLinks(message.content)"></span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 消息时间和状态 -->
|
2025-07-25 13:02:19 +08:00
|
|
|
<div
|
|
|
|
|
class="mt-1 flex items-center gap-1 px-2 text-xs text-gray-400 dark:text-gray-500"
|
|
|
|
|
>
|
2025-07-19 16:47:52 +08:00
|
|
|
<span>{{ message.time }}</span>
|
2025-07-25 13:02:19 +08:00
|
|
|
<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>
|
2025-07-19 16:47:52 +08:00
|
|
|
</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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 16:03:07 +08:00
|
|
|
.bubble-bg {
|
|
|
|
|
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
|
|
|
|
}
|
|
|
|
|
.bubble-bg.dark {
|
|
|
|
|
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.message-bubble.received.bubble-bg {
|
2025-07-19 16:47:52 +08:00
|
|
|
background: white;
|
|
|
|
|
color: #1a202c;
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
border-bottom-left-radius: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 16:03:07 +08:00
|
|
|
.message-bubble.received.dark.bubble-bg {
|
2025-07-19 16:47:52 +08:00
|
|
|
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;
|
2025-07-25 13:02:19 +08:00
|
|
|
background: linear-gradient(
|
|
|
|
|
to bottom,
|
|
|
|
|
transparent 0%,
|
|
|
|
|
rgba(0, 0, 0, 0.3) 100%
|
|
|
|
|
);
|
2025-07-19 16:47:52 +08:00
|
|
|
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;
|
|
|
|
|
}
|
2025-07-31 16:03:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 处方卡片样式 - 使用TailwindCSS类替代 */
|
|
|
|
|
.prescription-card {
|
|
|
|
|
@apply w-full max-w-xs md:max-w-sm cursor-pointer rounded-xl bg-white dark:bg-gray-700 p-4 shadow-sm transition-all duration-300;
|
|
|
|
|
@apply border border-gray-200 dark:border-gray-600 hover:shadow-md hover:border-blue-300 dark:hover:border-blue-500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.message-bubble.sent .prescription-card {
|
|
|
|
|
@apply bg-blue-50/30 dark:bg-blue-900/30 border-blue-200/50 dark:border-blue-700/70;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.prescription-card:hover {
|
|
|
|
|
@apply transform -translate-y-0.5;
|
|
|
|
|
}
|
2025-07-19 16:47:52 +08:00
|
|
|
</style>
|