This commit is contained in:
Your Name
2026-03-27 18:06:12 +08:00
parent 9160c36735
commit 099bc1dd22
645 changed files with 276473 additions and 957 deletions
+19 -8
View File
@@ -26,7 +26,7 @@
<div v-else class="chat-content">
<UIKitProvider language="zh-CN" theme="light">
<div class="chat-layout">
<ConversationList class="conversation-list" />
<!-- 嵌入式问诊窗口仅与当前患者会话不展示全局会话列表 -->
<Chat class="chat-area">
<MessageList :conversationID="conversationId" />
<MessageInput>
@@ -129,7 +129,6 @@ import {
} from '@/utils/tuicall-error'
import useUserStore from '@/stores/modules/user'
import {
ConversationList,
Chat,
MessageList,
MessageInput,
@@ -183,6 +182,8 @@ const BIND_ROOM_MAX_ATTEMPTS = 40
const BIND_ROOM_RETRY_MS = 100
const localCallRecorder = new CallLocalRecorder()
/** 与 server .env [trtc] ISLOCHOSTVOD 一致:由 getCallSignature 下发,false 时不做浏览器本地录制 */
const isLochostVodEnabled = ref(false)
const localRecordingBadge = ref(false)
let localRecordingRound = 0
let startLocalRecTimer: ReturnType<typeof setTimeout> | null = null
@@ -199,6 +200,10 @@ function clearLocalRecordingStartTimer() {
}
}
function syncLochostVodFromSignature(res: { isLochostVod?: boolean }) {
isLochostVodEnabled.value = Boolean(res?.isLochostVod)
}
function finalizeLocalRecordingAndAttach(): Promise<void> {
if (finalizeLocalRecordingPromise) {
return finalizeLocalRecordingPromise
@@ -206,6 +211,9 @@ function finalizeLocalRecordingAndAttach(): Promise<void> {
finalizeLocalRecordingPromise = (async () => {
clearLocalRecordingStartTimer()
localRecordingBadge.value = false
if (!isLochostVodEnabled.value) {
return
}
localCallRecorder.beginStopNow()
const id = recordingDiagnosisIdSnapshot ?? diagnosisId.value
try {
@@ -621,8 +629,12 @@ function setupCallRoomBinding() {
if (next === CALL_STATUS_CONNECTED) {
void tryBindRoomIfReady()
clearLocalRecordingStartTimer()
if (!isLochostVodEnabled.value) {
previousCallStatus = next
return
}
const roundAtSchedule = localRecordingRound
// 立即尝试启动录制,不要等待 1200ms
const attemptStartRecording = (retryCount = 0) => {
if (roundAtSchedule !== localRecordingRound) {
@@ -889,6 +901,7 @@ watch(() => activeConversation.value, async (newConversation) => {
patient_id: newConversation.userProfile.userID.replace("patient_", ""),
diagnosis_id: diagnosisId.value
})
syncLochostVodFromSignature(res)
patientUserId.value = res.patientUserId
assistant_ids.value = res.assistant_id
console.log('会话切换后获取到 assistant_id:', assistant_ids.value, '完整响应:', res)
@@ -922,6 +935,7 @@ const open = async (data: { patientId: number; patientName: string; diagnosisId?
patient_id: data.patientId,
diagnosis_id: data.diagnosisId || 0
})
syncLochostVodFromSignature(res)
assistant_ids.value = res.assistant_id
console.log('后端返回的签名数据:', res)
console.log('assistant_id:', assistant_ids.value)
@@ -1062,6 +1076,7 @@ const startGroupVideoCall = async () => {
patient_id: patientId.value!,
diagnosis_id: diagnosisId.value
})
syncLochostVodFromSignature(res)
assistant_ids.value = res.assistant_id
console.log('重新获取 assistant_id:', assistant_ids.value)
}
@@ -1344,13 +1359,9 @@ defineExpose({ open })
height: 100%;
width: 100%;
.conversation-list {
width: 285px;
flex-shrink: 0;
}
.chat-area {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
position: relative;
+1 -1
View File
@@ -227,7 +227,7 @@ export default defineComponent({
}
const handlePreview = (url: string) => {
previewUrl.value = url
previewUrl.value = props.excludeDomain ? getImageUrl(url) : url
showPreview.value = true
}
@@ -0,0 +1,96 @@
<template>
<span v-if="disabled" class="medicine-name-readonly">{{ modelValue || '' }}</span>
<el-select
v-else
:model-value="modelValue"
class="medicine-name-select w-full"
filterable
remote
reserve-keyword
placeholder="搜索药材名称或拼音首字母"
:remote-method="remoteMethod"
:loading="loading"
clearable
@visible-change="onVisibleChange"
@update:model-value="onUpdate"
>
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.name" />
</el-select>
</template>
<script setup lang="ts">
import { medicineLists } from '@/api/medicine'
const props = withDefaults(
defineProps<{
modelValue: string
disabled?: boolean
}>(),
{ disabled: false }
)
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
const loading = ref(false)
const options = ref<{ id: number; name: string }[]>([])
function ensureCurrentInOptions() {
const v = (props.modelValue || '').trim()
if (!v) {
return
}
if (!options.value.some((o) => o.name === v)) {
options.value = [{ id: 0, name: v }, ...options.value]
}
}
const remoteMethod = async (query: string) => {
loading.value = true
try {
const q = (query || '').trim()
const res = await medicineLists({
name: q || undefined,
page_no: 1,
page_size: 100,
status: 1
})
options.value = res.lists || []
ensureCurrentInOptions()
} catch (e) {
console.error(e)
options.value = []
} finally {
loading.value = false
}
}
const onVisibleChange = (open: boolean) => {
if (open && options.value.length === 0) {
remoteMethod('')
}
}
const onUpdate = (val: string) => {
emit('update:modelValue', val || '')
}
watch(
() => props.modelValue,
() => {
ensureCurrentInOptions()
},
{ immediate: true }
)
</script>
<style scoped lang="scss">
.medicine-name-readonly {
color: var(--el-text-color-regular);
}
.medicine-name-select.w-full {
width: 100%;
}
</style>
+49 -14
View File
@@ -52,7 +52,7 @@
<el-input v-model="formData.tongue" placeholder="舌象" />
</el-form-item>
</el-col>
<el-col :span="12">
<!-- <el-col :span="12">
<el-form-item label="舌象详情" prop="tongue_image">
<el-input v-model="formData.tongue_image" placeholder="舌象详情" />
</el-form-item>
@@ -61,7 +61,7 @@
<el-form-item label="脉象详情" prop="pulse_condition">
<el-input v-model="formData.pulse_condition" placeholder="脉象详情" />
</el-form-item>
</el-col>
</el-col> -->
<el-col :span="24">
<el-form-item label="临床诊断" prop="clinical_diagnosis">
<el-input
@@ -93,28 +93,29 @@
<span class="ml-2 text-gray-500"></span>
</el-form-item>
</div>
<div v-for="(herb, index) in formData.herbs" :key="index" class="herb-item">
<el-form-item :label="`中药${index + 1}`">
<div class="flex gap-2 items-center flex-wrap">
<el-input
v-model="herb.name"
placeholder="药品名称"
style="width: 160px"
/>
<div class="herbs-editor-grid">
<div
v-for="(herb, index) in formData.herbs"
:key="'herb-' + index"
class="herb-editor-card"
>
<div class="herb-editor-card__title">中药 {{ index + 1 }}</div>
<MedicineNameSelect v-model="herb.name" class="herb-editor-card__select" />
<div class="herb-editor-card__row">
<el-input-number
v-model="herb.dosage"
:min="0.1"
:step="1"
:precision="1"
placeholder="剂量(克)"
style="width: 120px"
class="herb-editor-card__dose"
/>
<span class="text-gray-500"></span>
<span class="text-gray-500 shrink-0"></span>
<el-button type="danger" size="small" link @click="handleRemoveHerb(index)">
删除
</el-button>
</div>
</el-form-item>
</div>
</div>
</div>
@@ -547,6 +548,7 @@
</template>
<script setup lang="ts">
import MedicineNameSelect from '@/components/medicine-name-select/index.vue'
import feedback from '@/utils/feedback'
import { prescriptionAdd, prescriptionDetail, prescriptionGetByAppointment, prescriptionVoid, tcmDiagnosisDetail, prescriptionLibraryLists } from '@/api/tcm'
import { getDictData } from '@/api/app'
@@ -1253,9 +1255,42 @@ defineExpose({
align-items: center;
}
.herb-item {
.herbs-editor-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.herb-editor-card {
min-width: 0;
padding: 10px;
background: #fff;
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
}
.herb-editor-card__title {
font-size: 12px;
color: #909399;
margin-bottom: 6px;
}
.herb-editor-card__select {
width: 100%;
margin-bottom: 8px;
}
.herb-editor-card__row {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.herb-editor-card__dose {
flex: 1;
min-width: 88px;
}
}
}