This commit is contained in:
Your Name
2026-03-13 14:08:52 +08:00
parent 08dd9cd307
commit eb283f008b
667 changed files with 8425 additions and 27053 deletions
+106 -69
View File
@@ -136,7 +136,7 @@
</template>
</el-table-column>
<el-table-column label="操作" width="360" fixed="right">
<el-table-column label="操作" width="320" fixed="right">
<template #default="{ row }">
<el-button
v-perms="['doctor.appointment/detail']"
@@ -154,22 +154,6 @@
>
编辑患者
</el-button>
<el-button
v-perms="['tcm.diagnosis/video-call']"
type="success"
link
@click="handleVideoCall(row)"
>
1V1通话
</el-button>
<el-button
v-perms="['tcm.diagnosis/video-group']"
type="success"
link
@click="handleGroupVideoCall(row)"
>
群通话
</el-button>
<el-button
v-if="row.status === 1"
v-perms="['doctor.appointment/cancel']"
@@ -186,9 +170,9 @@
link
@click="handleComplete(row)"
>
完成视频面诊
完成面诊
</el-button>
<el-button
<el-button
v-if="row.status === 1"
v-perms="['doctor.appointment/prescription']"
type="success"
@@ -197,6 +181,15 @@
>
聊天
</el-button>
<el-button
v-if="row.status === 1"
v-perms="['tcm.diagnosis/guahao']"
type="primary"
link
@click="handleMiniProgramQRCode(row)"
>
确认诊单二维码
</el-button>
</template>
</el-table-column>
</el-table>
@@ -257,27 +250,59 @@
<!-- 编辑患者弹窗 -->
<edit-popup ref="editRef" @success="getLists" />
<!-- 视频通话组件 -->
<video-call ref="videoCallRef" />
<!-- 处方单抽屉 -->
<prescription-drawer ref="prescriptionDrawerRef" @success="loadData" />
<!-- 聊天对话框 -->
<chat-dialog ref="chatDialogRef" />
<!-- 小程序二维码弹窗 -->
<el-dialog
v-model="qrcodeDialogVisible"
title="小程序二维码"
width="400px"
:close-on-click-modal="false"
>
<div class="flex flex-col items-center justify-center py-4">
<div v-if="qrcodeLoading" class="text-center">
<el-icon class="is-loading" :size="40">
<Loading />
</el-icon>
<div class="mt-4 text-gray-500">生成中...</div>
</div>
<div v-else-if="qrcodeUrl" class="text-center">
<img :src="qrcodeUrl" alt="小程序二维码" class="w-64 h-64 border border-gray-200 rounded" />
<div class="mt-4 text-sm text-gray-600">
<div>患者{{ currentQRCodeRow?.patient_name }}</div>
<div class="mt-2">请使用微信扫描二维码</div>
<div class="mt-1 text-xs text-gray-400">跳转小程序路径pages/login/login</div>
</div>
</div>
<div v-else class="text-center text-gray-500">
生成失败请重试
</div>
</div>
<template #footer>
<el-button @click="qrcodeDialogVisible = false">关闭</el-button>
<el-button v-if="!qrcodeLoading" type="primary" @click="handleRegenerateQRCode">
重新生成
</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { usePaging } from '@/hooks/usePaging'
import { appointmentLists, cancelAppointment, completeAppointment, appointmentDetail } from '@/api/doctor'
import { getCallSignature } from '@/api/tcm'
import { getCallSignature, generateMiniProgramQrcode } from '@/api/tcm'
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'
import { getWeappConfig } from '@/api/channel/weapp'
import { Loading } from '@element-plus/icons-vue'
const userStore = useUserStore()
const userInfo = computed(() => userStore.userInfo)
@@ -367,10 +392,15 @@ const handleReset = () => {
const detailVisible = ref(false)
const detailData = ref<any>(null)
const editRef = ref()
const videoCallRef = ref()
const prescriptionDrawerRef = ref()
const chatDialogRef = ref()
// 小程序二维码相关
const qrcodeDialogVisible = ref(false)
const qrcodeLoading = ref(false)
const qrcodeUrl = ref('')
const currentQRCodeRow = ref<any>(null)
// 查看详情
const handleDetail = async (row: any) => {
try {
@@ -416,50 +446,6 @@ const handleEditPatient = (row: any) => {
editRef.value?.open('edit', row.patient_id)
}
// 1v1视频通话
const handleVideoCall = (row: any) => {
if (!row.patient_id) {
feedback.msgWarning('患者信息不完整')
return
}
videoCallRef.value?.open({
diagnosisId: row.diagnosis_id || row.id,
patientId: row.patient_id,
patientName: row.patient_name,
userId: `patient_${row.patient_id}`,
isGroup: false
})
}
// 群组视频通话
const handleGroupVideoCall = (row: any) => {
if (!row.patient_id) {
feedback.msgWarning('患者信息不完整')
return
}
if (!row.doctor_id) {
feedback.msgWarning('该预约没有医生信息,无法发起群组通话')
return
}
// 群组通话参与者:患者和医生
const userIds = [
`patient_${row.patient_id}`,
`doctor_${row.doctor_id}`
]
videoCallRef.value?.open({
diagnosisId: row.diagnosis_id || row.id,
patientId: row.patient_id,
patientName: row.patient_name,
doctorId: row.doctor_id,
userIds: userIds,
isGroup: true
})
}
// 聊天
const handleChat = async (row: any) => {
if (!row.patient_id) {
@@ -495,6 +481,57 @@ const handleChat = async (row: any) => {
}
}
// 生成小程序二维码(跳转小程序路径:pages/login/login
const handleMiniProgramQRCode = async (row: any) => {
if (!row.patient_id) {
feedback.msgWarning('患者信息不完整')
return
}
currentQRCodeRow.value = row
qrcodeDialogVisible.value = true
qrcodeLoading.value = true
qrcodeUrl.value = ''
try {
// 获取小程序配置
const config = await getWeappConfig()
if (!config?.app_id) {
feedback.msgError('小程序未配置,请先配置小程序信息')
qrcodeDialogVisible.value = false
return
}
const currentUser = userStore.userInfo
const result = await generateMiniProgramQrcode({
diagnosis_id: row.diagnosis_id || row.id,
patient_id: row.patient_id,
share_user_id: currentUser?.id || '',
mini_program_path: 'pages/login/login'
})
if (result?.qrcode_url) {
qrcodeUrl.value = result.qrcode_url
} else {
feedback.msgError('二维码生成失败')
}
} catch (error: any) {
console.error('生成小程序二维码失败:', error)
feedback.msgError(error?.msg || '生成二维码失败,请重试')
} finally {
qrcodeLoading.value = false
}
}
// 重新生成二维码
const handleRegenerateQRCode = () => {
if (currentQRCodeRow.value) {
handleMiniProgramQRCode(currentQRCodeRow.value)
}
}
loadData()
</script>
File diff suppressed because it is too large Load Diff
+228 -67
View File
@@ -16,7 +16,7 @@
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
label-width="160px"
>
<!-- 基本信息 -->
<el-divider content-position="left">基本信息</el-divider>
@@ -94,6 +94,98 @@
</el-col>
</el-row>
<!-- 生命体征 -->
<el-divider content-position="left">生命体征</el-divider>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="婚姻状态" prop="marital_status">
<el-select
v-model="formData.marital_status"
placeholder="请选择婚姻状态"
class="w-full"
:popper-options="{ strategy: 'fixed' }"
popper-class="high-z-index"
>
<el-option label="未婚" :value="0" />
<el-option label="已婚" :value="1" />
<el-option label="离异" :value="2" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="身高(cm)" prop="height">
<el-input-number
v-model="formData.height"
:min="0"
:max="300"
placeholder="请输入身高"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="体重(kg)" prop="weight">
<el-input-number
v-model="formData.weight"
:min="0"
:max="500"
:step="0.1"
placeholder="请输入体重"
class="w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="地区" prop="region">
<el-input
v-model="formData.region"
placeholder="请输入地区"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="收缩压(mmHg)" prop="systolic_pressure">
<el-input-number
v-model="formData.systolic_pressure"
:min="0"
:max="250"
placeholder="请输入收缩压"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="舒张压(mmHg)" prop="diastolic_pressure">
<el-input-number
v-model="formData.diastolic_pressure"
:min="0"
:max="150"
placeholder="请输入舒张压"
class="w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="空腹血糖(mmol/L)" prop="fasting_blood_sugar">
<el-input-number
v-model="formData.fasting_blood_sugar"
:min="0"
:max="50"
:step="0.1"
placeholder="请输入空腹血糖"
class="w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="诊断类型" prop="diagnosis_type">
@@ -161,144 +253,186 @@
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">主诉</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="发现糖尿病就患病史" prop="diabetes_discovery_year">
<el-input-number
v-model="formData.diabetes_discovery_year"
:min="0"
:max="100"
placeholder="请输入年数"
class="w-full"
/>
<template #append></template>
</el-form-item>
</el-col>
</el-row>
<!-- 现病史 -->
<el-form-item label="当地医院诊断结果" class="checkbox-form-item">
<el-checkbox-group v-model="formData.local_hospital_diagnosis">
<el-checkbox-button label="糖尿病">糖尿病</el-checkbox-button>
<el-checkbox-button label="高血压">高血压</el-checkbox-button>
<el-checkbox-button label="糖尿病高血压">糖尿病高血压</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
<el-form-item label="当地就诊医院名称" prop="local_hospital_name">
<el-input
v-model="formData.local_hospital_name"
placeholder="请输入当地就诊医院名称(不能填写当地、本地、互联网等关键词)"
/>
</el-form-item>
<el-divider content-position="left">现病史</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="口腔感觉" class="compact-form-item">
<el-radio-group v-model="formData.appetite">
<el-radio v-for="item in appetiteOptions" :key="item.value" :label="item.value">
<el-radio-button v-for="item in appetiteOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
</el-row>
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="每日饮水量" class="compact-form-item">
<el-radio-group v-model="formData.water_intake">
<el-radio v-for="item in waterIntakeOptions" :key="item.value" :label="item.value">
<el-radio-button v-for="item in waterIntakeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="体重变化" class="compact-form-item">
<el-form-item label="近一个月体重变化" class="compact-form-item">
<el-radio-group v-model="formData.weight_change">
<el-radio v-for="item in weightChangeOptions" :key="item.value" :label="item.value">
<el-radio-button v-for="item in weightChangeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="脂肪肝程度" class="compact-form-item">
<el-radio-group v-model="formData.fatty_liver_degree">
<el-radio v-for="item in fattyLiverDegreeOptions" :key="item.value" :label="item.value">
<el-radio-button v-for="item in fattyLiverDegreeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-row>
<el-form-item label="饮食情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.diet_condition">
<el-checkbox v-for="item in dietConditionOptions" :key="item.value" :label="item.value">
<el-checkbox-button v-for="item in dietConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
<el-form-item label="肢体感觉" class="checkbox-form-item">
<el-checkbox-group v-model="formData.body_feeling">
<el-checkbox v-for="item in bodyFeelingOptions" :key="item.value" :label="item.value">
<el-checkbox-button v-for="item in bodyFeelingOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
<el-form-item label="睡眠情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.sleep_condition">
<el-checkbox v-for="item in sleepConditionOptions" :key="item.value" :label="item.value">
<el-checkbox-button v-for="item in sleepConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
<el-form-item label="眼睛情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.eye_condition">
<el-checkbox v-for="item in eyeConditionOptions" :key="item.value" :label="item.value">
<el-checkbox-button v-for="item in eyeConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
<el-form-item label="头部感觉" class="checkbox-form-item">
<el-checkbox-group v-model="formData.head_feeling">
<el-checkbox v-for="item in headFeelingOptions" :key="item.value" :label="item.value">
<el-checkbox-button v-for="item in headFeelingOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
<el-form-item label="出汗情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.sweat_condition">
<el-checkbox v-for="item in sweatConditionOptions" :key="item.value" :label="item.value">
<el-checkbox-button v-for="item in sweatConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
<el-form-item label="皮肤情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.skin_condition">
<el-checkbox v-for="item in skinConditionOptions" :key="item.value" :label="item.value">
<el-checkbox-button v-for="item in skinConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
<el-form-item label="小便情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.urine_condition">
<el-checkbox v-for="item in urineConditionOptions" :key="item.value" :label="item.value">
<el-checkbox-button v-for="item in urineConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
<el-form-item label="大便情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.stool_condition">
<el-checkbox v-for="item in stoolConditionOptions" :key="item.value" :label="item.value">
<el-checkbox-button v-for="item in stoolConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
<el-form-item label="腰肾情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.kidney_condition">
<el-checkbox v-for="item in kidneyConditionOptions" :key="item.value" :label="item.value">
<el-checkbox-button v-for="item in kidneyConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
<el-form-item label="病逝补充" prop="symptoms">
<el-input
v-model="formData.symptoms"
type="textarea"
:rows="3"
placeholder="请输入症状"
/>
</el-form-item>
<!-- 既往史 -->
<el-divider content-position="left">既往史</el-divider>
<el-form-item label="既往史" prop="past_history">
<el-form-item label="" prop="past_history" class="checkbox-form-item">
<el-checkbox-group v-model="formData.past_history">
<el-checkbox
<el-checkbox-button
v-for="item in pastHistoryOptions"
:key="item.value"
:label="item.value"
>
{{ item.name }}
</el-checkbox>
</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
@@ -309,24 +443,24 @@
<el-col :span="8">
<el-form-item label="外伤史">
<el-radio-group v-model="formData.trauma_history">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
<el-radio-button :label="1"></el-radio-button>
<el-radio-button :label="0"></el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="手术史">
<el-radio-group v-model="formData.surgery_history">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
<el-radio-button :label="1"></el-radio-button>
<el-radio-button :label="0"></el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="过敏史">
<el-radio-group v-model="formData.allergy_history">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
<el-radio-button :label="1"></el-radio-button>
<el-radio-button :label="0"></el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
@@ -336,16 +470,16 @@
<el-col :span="12">
<el-form-item label="家族病史">
<el-radio-group v-model="formData.family_history">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
<el-radio-button :label="1"></el-radio-button>
<el-radio-button :label="0"></el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="妊娠哺乳史">
<el-radio-group v-model="formData.pregnancy_history">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
<el-radio-button :label="1"></el-radio-button>
<el-radio-button :label="0"></el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
@@ -372,14 +506,6 @@
<div class="form-tips">支持上传检查报告病历彩超等影像科检查图片最多10个文件</div>
</el-form-item>
<el-form-item label="症状" prop="symptoms">
<el-input
v-model="formData.symptoms"
type="textarea"
:rows="3"
placeholder="请输入症状"
/>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
@@ -494,7 +620,16 @@ const formData = ref({
diagnosis_type: '',
syndrome_type: '',
diabetes_type: '',
// 现病史字段
diabetes_discovery_year: undefined as number | undefined,
local_hospital_diagnosis: [],
local_hospital_name: '',
marital_status: undefined as number | undefined,
height: undefined as number | undefined,
weight: undefined as number | undefined,
region: '',
systolic_pressure: undefined as number | undefined,
diastolic_pressure: undefined as number | undefined,
fasting_blood_sugar: undefined as number | undefined,
appetite: '',
water_intake: '',
diet_condition: [],
@@ -509,18 +644,14 @@ const formData = ref({
stool_condition: [],
kidney_condition: [],
fatty_liver_degree: '',
// 既往史
past_history: [],
// 其他病史
trauma_history: 0,
surgery_history: 0,
allergy_history: 0,
family_history: 0,
pregnancy_history: 0,
// 文件上传
tongue_images: [],
report_files: [],
// 诊断信息
symptoms: '',
tongue_coating: '',
pulse: '',
@@ -746,11 +877,17 @@ const handleClose = () => {
id_card: '',
phone: '',
gender: 1,
age: undefined as number | undefined,
diagnosis_date: '',
diagnosis_type: '',
syndrome_type: '',
diabetes_type: '',
marital_status: undefined as number | undefined,
height: undefined as number | undefined,
weight: undefined as number | undefined,
region: '',
systolic_pressure: undefined as number | undefined,
diastolic_pressure: undefined as number | undefined,
fasting_blood_sugar: undefined as number | undefined,
diabetes_discovery_year: undefined as number | undefined,
local_hospital_diagnosis: [],
local_hospital_name: '',
appetite: '',
water_intake: '',
diet_condition: [],
@@ -792,15 +929,18 @@ defineExpose({
<style lang="scss" scoped>
.edit-drawer {
font-size: 12px;
:deep(.el-form) {
padding-bottom: 20px;
font-size: 12px;
}
:deep(.el-divider) {
margin: 24px 0 20px;
.el-divider__text {
font-size: 15px;
font-size: 13px;
font-weight: 600;
color: #303133;
background: #f5f7fa;
@@ -813,9 +953,30 @@ defineExpose({
}
:deep(.el-form-item__label) {
font-size: 12px;
font-weight: 500;
color: #606266;
}
:deep(.el-input__inner) {
font-size: 12px;
}
:deep(.el-textarea__inner) {
font-size: 12px;
}
:deep(.el-radio-button__inner) {
font-size: 12px;
}
:deep(.el-checkbox-button__inner) {
font-size: 12px;
}
:deep(.el-button) {
font-size: 12px;
}
// 紧凑型表单项(单选框)
.compact-form-item {
-778
View File
@@ -1,778 +0,0 @@
<template>
<div class="edit-drawer">
<el-drawer
v-model="visible"
:title="drawerTitle"
size="70%"
:before-close="handleClose"
>
<el-tabs v-model="activeTab" class="px-4">
<!-- 基本信息标签页 -->
<el-tab-pane label="基本信息" name="basic">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
>
<!-- 基本信息 -->
<el-divider content-position="left">基本信息</el-divider>
<el-form-item label="患者ID">
<el-input v-model="formData.patient_id" disabled placeholder="系统自动生成" />
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="患者姓名" prop="patient_name">
<el-input
v-model="formData.patient_name"
placeholder="请输入患者姓名"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="身份证号" prop="id_card">
<el-input
v-model="formData.id_card"
placeholder="请输入身份证号"
maxlength="18"
@blur="handleIdCardBlur"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="手机号" prop="phone">
<el-input
v-model="formData.phone"
placeholder="请输入手机号"
maxlength="11"
@blur="handlePhoneBlur"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="formData.gender">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="年龄" prop="age">
<el-input-number
v-model="formData.age"
:min="0"
:max="150"
placeholder="请输入年龄"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="诊断日期" prop="diagnosis_date">
<el-date-picker
v-model="formData.diagnosis_date"
type="date"
placeholder="请选择诊断日期"
value-format="YYYY-MM-DD"
class="w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="诊断类型" prop="diagnosis_type">
<el-select
v-model="formData.diagnosis_type"
placeholder="请选择诊断类型"
class="w-full"
>
<el-option
v-for="item in diagnosisTypeOptions"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="证型" prop="syndrome_type">
<el-select
v-model="formData.syndrome_type"
placeholder="请选择证型"
class="w-full"
>
<el-option
v-for="item in syndromeTypeOptions"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<!-- 现病史 -->
<el-divider content-position="left">现病史</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="口腔感觉">
<el-radio-group v-model="formData.appetite">
<el-radio v-for="item in appetiteOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="每日饮水量">
<el-radio-group v-model="formData.water_intake">
<el-radio v-for="item in waterIntakeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="饮食情况">
<el-checkbox-group v-model="formData.diet_condition">
<el-checkbox v-for="item in dietConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="体重变化">
<el-radio-group v-model="formData.weight_change">
<el-radio v-for="item in weightChangeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="肢体感觉">
<el-checkbox-group v-model="formData.body_feeling">
<el-checkbox v-for="item in bodyFeelingOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="睡眠情况">
<el-checkbox-group v-model="formData.sleep_condition">
<el-checkbox v-for="item in sleepConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="眼睛情况">
<el-checkbox-group v-model="formData.eye_condition">
<el-checkbox v-for="item in eyeConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="头部感觉">
<el-checkbox-group v-model="formData.head_feeling">
<el-checkbox v-for="item in headFeelingOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="出汗情况">
<el-checkbox-group v-model="formData.sweat_condition">
<el-checkbox v-for="item in sweatConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="皮肤情况">
<el-checkbox-group v-model="formData.skin_condition">
<el-checkbox v-for="item in skinConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="小便情况">
<el-checkbox-group v-model="formData.urine_condition">
<el-checkbox v-for="item in urineConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="大便情况">
<el-checkbox-group v-model="formData.stool_condition">
<el-checkbox v-for="item in stoolConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="腰肾情况">
<el-checkbox-group v-model="formData.kidney_condition">
<el-checkbox v-for="item in kidneyConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="脂肪肝程度">
<el-radio-group v-model="formData.fatty_liver_degree">
<el-radio v-for="item in fattyLiverDegreeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 既往史 -->
<el-divider content-position="left">既往史</el-divider>
<el-form-item label="既往史" prop="past_history">
<el-checkbox-group v-model="formData.past_history">
<el-checkbox
v-for="item in pastHistoryOptions"
:key="item.value"
:label="item.value"
>
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!-- 其他病史 -->
<el-divider content-position="left">其他病史</el-divider>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="外伤史">
<el-radio-group v-model="formData.trauma_history">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="手术史">
<el-radio-group v-model="formData.surgery_history">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="过敏史">
<el-radio-group v-model="formData.allergy_history">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="家族病史">
<el-radio-group v-model="formData.family_history">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="妊娠哺乳史">
<el-radio-group v-model="formData.pregnancy_history">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<!-- 诊断信息 -->
<el-divider content-position="left">诊断信息</el-divider>
<el-form-item label="舌苔照片">
<material-picker
v-model="formData.tongue_images"
:limit="9"
type="image"
/>
<div class="form-tips">支持上传多张舌苔照片最多9张</div>
</el-form-item>
<el-form-item label="检查报告">
<material-picker
v-model="formData.report_files"
:limit="10"
type="image"
/>
<div class="form-tips">支持上传检查报告病历彩超等影像科检查图片最多10个文件</div>
</el-form-item>
<el-form-item label="症状" prop="symptoms">
<el-input
v-model="formData.symptoms"
type="textarea"
:rows="3"
placeholder="请输入症状"
/>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="舌苔" prop="tongue_coating">
<el-input
v-model="formData.tongue_coating"
placeholder="请输入舌苔情况"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="脉象" prop="pulse">
<el-input
v-model="formData.pulse"
placeholder="请输入脉象"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="治则" prop="treatment_principle">
<el-input
v-model="formData.treatment_principle"
type="textarea"
:rows="3"
placeholder="请输入治则"
/>
</el-form-item>
<el-form-item label="处方" prop="prescription">
<el-input
v-model="formData.prescription"
type="textarea"
:rows="3"
placeholder="请输入处方"
/>
</el-form-item>
<el-form-item label="医嘱" prop="doctor_advice">
<el-input
v-model="formData.doctor_advice"
type="textarea"
:rows="3"
placeholder="请输入医嘱"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
type="textarea"
:rows="2"
placeholder="请输入备注"
/>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 血糖血压记录标签页 -->
<el-tab-pane label="血糖血压记录" name="blood" :disabled="!formData.id">
<blood-record-list
v-if="formData.id"
:diagnosis-id="Number(formData.id)"
:patient-id="Number(formData.patient_id)"
/>
<el-empty v-else description="请先保存诊单后再添加血糖血压记录" />
</el-tab-pane>
</el-tabs>
<template #footer>
<div class="flex justify-end gap-3 px-4 pb-4">
<el-button @click="handleClose">取消</el-button>
<el-button
v-if="activeTab === 'basic'"
type="primary"
@click="handleSubmit"
:loading="submitting"
>
确定
</el-button>
</div>
</template>
</el-drawer>
</div>
</template>
<script setup lang="ts">
import { tcmDiagnosisAdd, tcmDiagnosisEdit, tcmDiagnosisDetail, checkPhone, checkIdCard } from '@/api/tcm'
import { getDictData } from '@/api/app'
import feedback from '@/utils/feedback'
import { ElMessage } from 'element-plus'
import BloodRecordList from './components/BloodRecordList.vue'
const emit = defineEmits(['success'])
const visible = ref(false)
const formRef = ref()
const mode = ref('add')
const submitting = ref(false)
const activeTab = ref('basic')
const drawerTitle = computed(() => (mode.value === 'add' ? '新增诊单' : '编辑诊单'))
const formData = ref({
id: '',
patient_id: '',
patient_name: '',
id_card: '',
phone: '',
gender: 1,
age: undefined as number | undefined,
diagnosis_date: '',
diagnosis_type: '',
syndrome_type: '',
// 现病史字段
appetite: '',
water_intake: '',
diet_condition: [],
weight_change: '',
body_feeling: [],
sleep_condition: [],
eye_condition: [],
head_feeling: [],
sweat_condition: [],
skin_condition: [],
urine_condition: [],
stool_condition: [],
kidney_condition: [],
fatty_liver_degree: '',
// 既往史
past_history: [],
// 其他病史
trauma_history: 0,
surgery_history: 0,
allergy_history: 0,
family_history: 0,
pregnancy_history: 0,
// 文件上传
tongue_images: [],
report_files: [],
// 诊断信息
symptoms: '',
tongue_coating: '',
pulse: '',
treatment_principle: '',
prescription: '',
doctor_advice: '',
remark: '',
status: 1
})
// 自定义验证规则
const validatePhone = (rule: any, value: any, callback: any) => {
if (!value) {
callback(new Error('请输入手机号'))
return
}
if (!/^1[3-9]\d{9}$/.test(value)) {
callback(new Error('手机号格式不正确'))
return
}
callback()
}
const validateIdCard = (rule: any, value: any, callback: any) => {
if (value && !/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(value)) {
callback(new Error('身份证号格式不正确'))
return
}
callback()
}
const formRules = {
patient_name: [{ required: true, message: '请输入患者姓名', trigger: 'blur' }],
id_card: [{ validator: validateIdCard, trigger: 'blur' }],
phone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
age: [{ required: true, message: '请输入年龄', trigger: 'blur' }],
diagnosis_date: [{ required: true, message: '请选择诊断日期', trigger: 'change' }],
diagnosis_type: [{ required: true, message: '请选择诊断类型', trigger: 'change' }],
syndrome_type: [{ required: true, message: '请选择证型', trigger: 'change' }]
}
// 获取字典选项
const diagnosisTypeOptions = ref<any[]>([])
const syndromeTypeOptions = ref<any[]>([])
const pastHistoryOptions = ref<any[]>([])
// 现病史字典选项
const appetiteOptions = ref<any[]>([])
const waterIntakeOptions = ref<any[]>([])
const dietConditionOptions = ref<any[]>([])
const weightChangeOptions = ref<any[]>([])
const bodyFeelingOptions = ref<any[]>([])
const sleepConditionOptions = ref<any[]>([])
const eyeConditionOptions = ref<any[]>([])
const headFeelingOptions = ref<any[]>([])
const sweatConditionOptions = ref<any[]>([])
const skinConditionOptions = ref<any[]>([])
const urineConditionOptions = ref<any[]>([])
const stoolConditionOptions = ref<any[]>([])
const kidneyConditionOptions = ref<any[]>([])
const fattyLiverDegreeOptions = ref<any[]>([])
const getDictOptions = async () => {
try {
const [
diagnosisType,
syndromeType,
pastHistory,
appetite,
waterIntake,
dietCondition,
weightChange,
bodyFeeling,
sleepCondition,
eyeCondition,
headFeeling,
sweatCondition,
skinCondition,
urineCondition,
stoolCondition,
kidneyCondition,
fattyLiverDegree
] = await Promise.all([
getDictData({ type: 'diagnosis_type' }),
getDictData({ type: 'syndrome_type' }),
getDictData({ type: 'past_history' }),
getDictData({ type: 'appetite' }),
getDictData({ type: 'water_intake' }),
getDictData({ type: 'diet_condition' }),
getDictData({ type: 'weight_change' }),
getDictData({ type: 'body_feeling' }),
getDictData({ type: 'sleep_condition' }),
getDictData({ type: 'eye_condition' }),
getDictData({ type: 'head_feeling' }),
getDictData({ type: 'sweat_condition' }),
getDictData({ type: 'skin_condition' }),
getDictData({ type: 'urine_condition' }),
getDictData({ type: 'stool_condition' }),
getDictData({ type: 'kidney_condition' }),
getDictData({ type: 'fatty_liver_degree' })
])
diagnosisTypeOptions.value = diagnosisType?.diagnosis_type || []
syndromeTypeOptions.value = syndromeType?.syndrome_type || []
pastHistoryOptions.value = pastHistory?.past_history || []
appetiteOptions.value = appetite?.appetite || []
waterIntakeOptions.value = waterIntake?.water_intake || []
dietConditionOptions.value = dietCondition?.diet_condition || []
weightChangeOptions.value = weightChange?.weight_change || []
bodyFeelingOptions.value = bodyFeeling?.body_feeling || []
sleepConditionOptions.value = sleepCondition?.sleep_condition || []
eyeConditionOptions.value = eyeCondition?.eye_condition || []
headFeelingOptions.value = headFeeling?.head_feeling || []
sweatConditionOptions.value = sweatCondition?.sweat_condition || []
skinConditionOptions.value = skinCondition?.skin_condition || []
urineConditionOptions.value = urineCondition?.urine_condition || []
stoolConditionOptions.value = stoolCondition?.stool_condition || []
kidneyConditionOptions.value = kidneyCondition?.kidney_condition || []
fattyLiverDegreeOptions.value = fattyLiverDegree?.fatty_liver_degree || []
} catch (error) {
console.error('获取字典数据失败:', error)
}
}
// 手机号失焦检查
const handlePhoneBlur = async () => {
if (!formData.value.phone || !/^1[3-9]\d{9}$/.test(formData.value.phone)) {
return
}
try {
const result = await checkPhone({
phone: formData.value.phone,
id: formData.value.id || ''
})
if (result.exists) {
ElMessage.warning(result.message)
}
} catch (error) {
console.error('检查手机号失败:', error)
}
}
// 身份证号失焦检查
const handleIdCardBlur = async () => {
if (!formData.value.id_card || !/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(formData.value.id_card)) {
return
}
try {
const result = await checkIdCard({
id_card: formData.value.id_card,
id: formData.value.id || ''
})
if (result.exists) {
ElMessage.warning(result.message)
}
} catch (error) {
console.error('检查身份证号失败:', error)
}
}
const open = async (type: string, id?: number) => {
mode.value = type
visible.value = true
activeTab.value = 'basic' // 重置到基本信息标签页
// 加载字典数据
await getDictOptions()
if (type === 'edit' && id) {
const data = await tcmDiagnosisDetail({ id })
formData.value = data
}
}
const handleSubmit = async () => {
await formRef.value?.validate()
submitting.value = true
try {
if (mode.value === 'add') {
const result = await tcmDiagnosisAdd(formData.value)
feedback.msgSuccess('添加成功')
// 如果是新增,保存成功后切换到编辑模式,这样可以添加血糖血压记录
if (result && result.id) {
mode.value = 'edit'
formData.value.id = result.id
// 重新获取详情,确保patient_id等字段正确
try {
const detail = await tcmDiagnosisDetail({ id: result.id })
formData.value.patient_id = detail.patient_id
feedback.msgSuccess('诊单已保存,现在可以添加血糖血压记录了')
} catch (error) {
console.error('获取详情失败:', error)
// 即使获取详情失败,也不影响主流程
}
}
} else {
await tcmDiagnosisEdit(formData.value)
feedback.msgSuccess('编辑成功')
}
emit('success')
} catch (error) {
console.error('提交失败:', error)
} finally {
submitting.value = false
}
}
const handleClose = () => {
formRef.value?.resetFields()
formData.value = {
id: '',
patient_id: '',
patient_name: '',
id_card: '',
phone: '',
gender: 1,
age: undefined as number | undefined,
diagnosis_date: '',
diagnosis_type: '',
syndrome_type: '',
appetite: '',
water_intake: '',
diet_condition: [],
weight_change: '',
body_feeling: [],
sleep_condition: [],
eye_condition: [],
head_feeling: [],
sweat_condition: [],
skin_condition: [],
urine_condition: [],
stool_condition: [],
kidney_condition: [],
fatty_liver_degree: '',
past_history: [],
trauma_history: 0,
surgery_history: 0,
allergy_history: 0,
family_history: 0,
pregnancy_history: 0,
tongue_images: [],
report_files: [],
symptoms: '',
tongue_coating: '',
pulse: '',
treatment_principle: '',
prescription: '',
doctor_advice: '',
remark: '',
status: 1
}
visible.value = false
}
defineExpose({
open
})
</script>
<style lang="scss" scoped>
.edit-drawer {
:deep(.el-checkbox-group) {
.el-checkbox {
margin-right: 20px;
margin-bottom: 10px;
}
}
:deep(.el-radio-group) {
.el-radio {
margin-right: 20px;
margin-bottom: 10px;
}
}
.form-tips {
font-size: 12px;
color: #999;
margin-top: 5px;
}
}
</style>
+10 -57
View File
@@ -55,7 +55,7 @@
<el-card class="!border-none mt-4" shadow="never">
<div class="mb-4 flex justify-between items-center">
<div>
<el-button type="primary" @click="handleAdd" v-perms="['tcm.diagnosis/add']">新增患者</el-button>
<el-button type="primary" @click="handleAdd" v-perms="['tcm.diagnosis/add']">新增诊单</el-button>
<el-button
v-perms="['tcm.diagnosis/assign']"
type="success"
@@ -76,8 +76,8 @@
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column label="ID" prop="id" width="80" />
<el-table-column label="患者姓名" prop="patient_name" min-width="100" />
<el-table-column label="账户编号" prop="id" width="80" />
<el-table-column label="账户姓名" prop="patient_name" min-width="100" />
<el-table-column label="性别" prop="gender_desc" width="80" />
<el-table-column label="年龄" prop="age" width="80" />
<el-table-column label="诊断日期" prop="diagnosis_date_text" min-width="120" />
@@ -91,7 +91,7 @@
<span>{{ getDictLabel(syndromeTypeOptions, row.syndrome_type) }}</span>
</template>
</el-table-column>
<el-table-column label="指派医助" prop="assistant_name" min-width="100">
<el-table-column label="归属助理" prop="assistant_name" min-width="100">
<template #default="{ row }">
<span>{{ getAssistantName(row.assistant_id || row.assistant) }}</span>
</template>
@@ -120,7 +120,7 @@
>
指派
</el-button>
<el-button
<!-- <el-button
v-perms="['tcm.diagnosis/video-call']"
type="success"
link
@@ -135,14 +135,14 @@
@click="handleGroupVideoCall(row)"
>
群通话
</el-button>
</el-button>-->
<el-button
v-perms="['tcm.diagnosis/guahao']"
type="success"
link
@click="handleAppointment(row)"
>
挂号
>
预约医生
</el-button>
<el-button
v-perms="['tcm.diagnosis/guahao']"
@@ -150,10 +150,10 @@
link
@click="handleMiniProgramQRCode(row)"
>
小程序二维码
确认诊单二维码
</el-button>
<el-button type="primary" link @click="handleEdit(row.id)" v-perms="['tcm.diagnosis/edit']">
编辑
诊单
</el-button>
<el-button type="danger" link @click="handleDelete(row.id)" v-perms="['tcm.diagnosis/delete']">
删除
@@ -168,7 +168,6 @@
<edit-popup ref="editRef" @success="getLists" />
<detail-popup ref="detailRef" />
<video-call ref="videoCallRef" />
<appointment-popup ref="appointmentRef" @success="getLists" />
<!-- 小程序二维码弹窗 -->
@@ -261,7 +260,6 @@ import feedback from '@/utils/feedback'
import { Loading } from '@element-plus/icons-vue'
import EditPopup from './edit.vue'
import DetailPopup from './detail.vue'
import VideoCall from '@/components/video-call/index.vue'
import AppointmentPopup from './appointment.vue'
import useUserStore from '@/stores/modules/user'
import dayjs from 'dayjs'
@@ -339,7 +337,6 @@ const { pager, getLists, resetParams, resetPage } = usePaging({
const editRef = ref()
const detailRef = ref()
const videoCallRef = ref()
const appointmentRef = ref()
const userStore = useUserStore()
@@ -443,50 +440,6 @@ const handleDelete = async (id: number) => {
getLists()
}
// 视频通话(一对一)
const handleVideoCall = (row: any) => {
if (!row.patient_id) {
feedback.msgWarning('患者信息不完整')
return
}
videoCallRef.value?.open({
diagnosisId: row.id,
patientId: row.patient_id,
patientName: row.patient_name,
userId: `patient_${row.patient_id}`, // 患者的用户ID
isGroup: false
})
}
// 群组视频通话(三方通话)
const handleGroupVideoCall = (row: any) => {
if (!row.patient_id) {
feedback.msgWarning('患者信息不完整')
return
}
if (!row.assistant_id) {
feedback.msgWarning('该诊单未指派医助,无法发起群组通话')
return
}
// 群组通话参与者顺序:当前登录用户(发起者)、患者、医助
const userIds = [
`patient_${row.patient_id}`, // 先邀请患者
`doctor_${row.assistant_id}` // 再邀请医助
]
videoCallRef.value?.open({
diagnosisId: row.id,
patientId: row.patient_id,
patientName: row.patient_name,
assistantId: row.assistant_id,
userIds: userIds,
isGroup: true
})
}
// 患者挂号
const handleAppointment = (row: any) => {
if (!row.patient_id) {
View File