新增功能
This commit is contained in:
@@ -51,6 +51,15 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// 动态加载 Chat UIKit 样式
|
||||
(function() {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = 'https://web.sdk.qcloud.com/im/assets/latest/index.css';
|
||||
document.head.appendChild(link);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
|
||||
Generated
+1376
-12
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -12,7 +12,8 @@
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@highlightjs/vue-plugin": "^2.1.0",
|
||||
"@trtc/calls-uikit-vue": "^4.2.2",
|
||||
"@tencentcloud/chat-uikit-vue3": "^4.5.4",
|
||||
"@trtc/calls-uikit-vue": "^4.4.6",
|
||||
"@vue/shared": "^3.5.13",
|
||||
"@vueuse/core": "^12.7.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}:root{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,*:after,*:before{box-sizing:border-box}ul,li{list-style:none;padding:0;margin:0}picture,img,video,canvas,svg{display:block;max-width:100%}img{max-width:100%;height:auto;vertical-align:middle;image-rendering:-webkit-optimize-contrast;aspect-ratio:attr(width)/attr(height);display:inline-block;-webkit-user-drag:none;-webkit-user-select:none;user-select:none}img:not([src],[srcset]){visibility:hidden}
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="side-tab" :class="{ dark: isDark }">
|
||||
<!-- 用户头像 -->
|
||||
<div class="avatar-wrapper">
|
||||
<Avatar class="avatar" :src="loginUserInfo?.avatarUrl" />
|
||||
<div class="tooltip">
|
||||
<div class="tooltip-name">{{ loginUserInfo?.userName || '未命名' }}</div>
|
||||
<div class="tooltip-id">ID: {{ loginUserInfo?.userId }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tab 切换 -->
|
||||
<div class="tabs">
|
||||
<div
|
||||
class="tab-item"
|
||||
:class="{ active: props.activeTab === 'conversations' }"
|
||||
@click="handleTabChange('conversations')"
|
||||
title="会话"
|
||||
>
|
||||
<IconChatNew size="24" />
|
||||
</div>
|
||||
<div
|
||||
class="tab-item"
|
||||
:class="{ active: props.activeTab === 'contacts' }"
|
||||
@click="handleTabChange('contacts')"
|
||||
title="联系人"
|
||||
>
|
||||
<IconContacts size="24" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { useLoginState, useUIKit, Avatar } from '@tencentcloud/chat-uikit-vue3';
|
||||
import { IconChatNew, IconContacts } from '@tencentcloud/uikit-base-component-vue3';
|
||||
|
||||
const { theme } = useUIKit();
|
||||
const { loginUserInfo } = useLoginState();
|
||||
|
||||
const isDark = computed(() => theme.value === 'dark' || theme.value === 'serious');
|
||||
|
||||
interface Props {
|
||||
activeTab?: 'conversations' | 'contacts';
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
activeTab: 'conversations'
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
tabChange: [tab: 'conversations' | 'contacts'];
|
||||
}>();
|
||||
|
||||
const handleTabChange = (tab: 'conversations' | 'contacts') => {
|
||||
emit('tabChange', tab);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.side-tab{width:72px;height:100vh;background:var(--bg-color-function);display:flex;flex-direction:column;align-items:center;padding:20px 0;transition:background 0.3s;}.avatar-wrapper{position:relative;margin-bottom:24px;cursor:pointer;}.avatar-wrapper:hover:deep(.avatar){transform:scale(1.05);box-shadow:0 4px 12px rgba(0,0,0,0.15);}.tooltip{position:absolute;left:60px;top:50%;transform:translateY(-50%);padding:8px 12px;background:rgba(0,0,0,0.85);color:#fff;border-radius:6px;white-space:nowrap;opacity:0;visibility:hidden;pointer-events:none;transition:all 0.3s;z-index:1000;}.tooltip::before{content:'';position:absolute;left:-6px;top:50%;transform:translateY(-50%);border:6px solid transparent;border-right-color:rgba(0,0,0,0.85);}.avatar-wrapper:hover .tooltip{opacity:1;visibility:visible;}.tooltip-name{font-size:14px;font-weight:500;margin-bottom:4px;}.tooltip-id{font-size:12px;opacity:0.8;}.tabs{display:flex;flex-direction:column;gap:16px;}.tab-item{width:48px;height:48px;display:flex;align-items:center;justify-content:center;border-radius:12px;cursor:pointer;transition:all 0.3s;color:var(--text-color-primary);}.tab-item:hover{background:rgba(0,0,0,0.05);}.tab-item.active{background:var(--button-color-primary-default);color:var(--text-color-button);}.side-tab.dark{background:#1a1a1a;}.side-tab.dark .avatar-wrapper:hover:deep(.avatar){box-shadow:0 4px 12px rgba(255,255,255,0.2);}.side-tab.dark .tooltip{background:rgba(255,255,255,0.95);color:#1a1a1a;}.side-tab.dark .tooltip::before{border-right-color:rgba(255,255,255,0.95);}.side-tab.dark .tab-item:hover{background:rgba(255,255,255,0.1);}.side-tab.dark .tab-item.active{background:#1890ff;color:#fff;}
|
||||
</style>
|
||||
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="`与 ${patientName} 聊天`"
|
||||
width="1200px"
|
||||
top="5vh"
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
:destroy-on-close="true"
|
||||
class="chat-dialog"
|
||||
>
|
||||
<div class="chat-container">
|
||||
<div v-if="!isReady" class="loading-container">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>{{ loadingText }}</span>
|
||||
</div>
|
||||
<div v-else-if="error" class="error-container">
|
||||
<el-alert :title="error" type="error" :closable="false" />
|
||||
</div>
|
||||
<div v-else class="chat-content">
|
||||
<UIKitProvider>
|
||||
<div class="chat-layout">
|
||||
<ConversationList class="conversation-list" />
|
||||
<Chat class="chat-area">
|
||||
<MessageList />
|
||||
<MessageInput />
|
||||
</Chat>
|
||||
</div>
|
||||
</UIKitProvider>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
import { getCallSignature } from '@/api/tcm'
|
||||
import feedback from '@/utils/feedback'
|
||||
import {
|
||||
ConversationList,
|
||||
Chat,
|
||||
MessageList,
|
||||
MessageInput,
|
||||
UIKitProvider,
|
||||
useLoginState
|
||||
} from '@tencentcloud/chat-uikit-vue3'
|
||||
import '@/assets/chat-uikit.css'
|
||||
|
||||
const visible = ref(false)
|
||||
const isReady = ref(false)
|
||||
const error = ref('')
|
||||
const patientName = ref('')
|
||||
const patientId = ref<number | null>(null)
|
||||
const diagnosisId = ref<number | null>(null)
|
||||
const loadingText = ref('正在初始化...')
|
||||
const patientUserId = ref('')
|
||||
|
||||
const { login, logout } = useLoginState()
|
||||
|
||||
const open = async (data: { patientId: number; patientName: string; diagnosisId?: number }) => {
|
||||
visible.value = true
|
||||
patientName.value = data.patientName
|
||||
patientId.value = data.patientId
|
||||
diagnosisId.value = data.diagnosisId || null
|
||||
error.value = ''
|
||||
isReady.value = false
|
||||
loadingText.value = '正在获取签名...'
|
||||
|
||||
try {
|
||||
// 获取医生的签名信息
|
||||
const res = await getCallSignature({
|
||||
patient_id: data.patientId,
|
||||
diagnosis_id: data.diagnosisId || 0
|
||||
})
|
||||
|
||||
console.log('后端返回的签名数据:', res)
|
||||
|
||||
if (!res || !res.userSig) {
|
||||
throw new Error('获取签名失败')
|
||||
}
|
||||
|
||||
patientUserId.value = res.patientUserId || `patient_${data.patientId}`
|
||||
|
||||
loadingText.value = '正在登录聊天系统...'
|
||||
|
||||
// 登录 IM
|
||||
await login({
|
||||
sdkAppId: Number(res.sdkAppId),
|
||||
userId: res.userId,
|
||||
userSig: res.userSig,
|
||||
useUploadPlugin: true
|
||||
})
|
||||
|
||||
console.log('IM 登录成功')
|
||||
|
||||
loadingText.value = '加载聊天界面...'
|
||||
|
||||
// 等待一下让组件完全初始化
|
||||
setTimeout(() => {
|
||||
isReady.value = true
|
||||
}, 500)
|
||||
|
||||
} catch (err: any) {
|
||||
console.error('初始化聊天失败:', err)
|
||||
error.value = err.message || '初始化聊天失败'
|
||||
feedback.msgError(error.value)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
// 登出
|
||||
logout()
|
||||
visible.value = false
|
||||
isReady.value = false
|
||||
error.value = ''
|
||||
patientId.value = null
|
||||
diagnosisId.value = null
|
||||
patientUserId.value = ''
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chat-dialog {
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 0;
|
||||
height: 75vh;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__header) {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
height: 75vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: #909399;
|
||||
|
||||
.el-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.error-container {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.chat-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-layout {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.conversation-list {
|
||||
width: 285px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -188,15 +188,15 @@
|
||||
>
|
||||
完成视频面诊
|
||||
</el-button>
|
||||
<!-- <el-button
|
||||
<el-button
|
||||
v-if="row.status === 1"
|
||||
v-perms="['doctor.appointment/prescription']"
|
||||
type="success"
|
||||
link
|
||||
@click="handleCreatePrescription(row)"
|
||||
>
|
||||
创建处方单
|
||||
</el-button> -->
|
||||
聊天
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -262,6 +262,9 @@
|
||||
|
||||
<!-- 处方单抽屉 -->
|
||||
<prescription-drawer ref="prescriptionDrawerRef" @success="loadData" />
|
||||
|
||||
<!-- 聊天对话框 -->
|
||||
<chat-dialog ref="chatDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -272,6 +275,7 @@ import feedback from '@/utils/feedback'
|
||||
import EditPopup from '../diagnosis/edit.vue'
|
||||
import VideoCall from '@/components/video-call/index.vue'
|
||||
import PrescriptionDrawer from './components/prescription-drawer.vue'
|
||||
import ChatDialog from '@/components/chat-dialog/index.vue'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
@@ -364,6 +368,7 @@ const detailData = ref<any>(null)
|
||||
const editRef = ref()
|
||||
const videoCallRef = ref()
|
||||
const prescriptionDrawerRef = ref()
|
||||
const chatDialogRef = ref()
|
||||
|
||||
// 查看详情
|
||||
const handleDetail = async (row: any) => {
|
||||
@@ -456,13 +461,22 @@ const handleGroupVideoCall = (row: any) => {
|
||||
|
||||
// 创建处方单
|
||||
const handleCreatePrescription = async (row: any) => {
|
||||
try {
|
||||
// 获取详细信息
|
||||
const detail = await appointmentDetail({ id: row.id })
|
||||
prescriptionDrawerRef.value?.open(detail)
|
||||
} catch (error) {
|
||||
feedback.msgError('获取患者信息失败')
|
||||
if (!row.patient_id) {
|
||||
feedback.msgWarning('患者信息不完整')
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有诊单ID
|
||||
if (!row.diagnosis_id && !row.id) {
|
||||
feedback.msgWarning('预约信息不完整,无法发起聊天')
|
||||
return
|
||||
}
|
||||
|
||||
chatDialogRef.value?.open({
|
||||
patientId: row.patient_id,
|
||||
patientName: row.patient_name,
|
||||
diagnosisId: row.diagnosis_id || row.id // 使用诊单ID,如果没有则使用预约ID
|
||||
})
|
||||
}
|
||||
|
||||
loadData()
|
||||
|
||||
Reference in New Issue
Block a user