Files
zyt/admin/src/views/tcm/diagnosis/edit.vue
T
2026-04-07 18:13:03 +08:00

1358 lines
52 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="edit-drawer">
<el-drawer
v-model="visible"
:title="drawerTitle"
size="60%"
:before-close="handleClose"
:z-index="1500"
:modal="true"
class="diagnosis-drawer"
>
<el-tabs v-model="activeTab" class="diagnosis-tabs">
<!-- 基本信息标签页 -->
<el-tab-pane label="病历" name="basic">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="160px"
>
<!-- 基本信息 -->
<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"
@focus="handleIdCardFocus"
@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"
@focus="handlePhoneFocus"
@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-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
v-model="formData.fasting_blood_sugar"
placeholder="请输入空腹血糖,如:8.5 或 8-9"
maxlength="20"
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"
:popper-options="{ strategy: 'fixed' }"
popper-class="high-z-index"
>
<el-option
v-for="item in diagnosisTypeOptions"
: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="12">
<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-row :gutter="20">
<el-col :span="12">
<el-form-item label="在用药物" prop="current_medications">
<el-input
v-model="formData.current_medications"
type="textarea"
:rows="3"
placeholder="请填写患者当前正在使用的药物(如降压药、降糖药等),多种请换行或顿号分隔"
maxlength="2000"
show-word-limit
/>
</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="diagnosis_date">
<el-date-picker
v-model="formData.diagnosis_date"
type="date"
placeholder="请选择诊断日期"
value-format="YYYY-MM-DD"
class="w-full"
:popper-options="{ strategy: 'fixed' }"
popper-class="high-z-index"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发现糖尿病患病史" prop="diabetes_discovery_year">
<el-input
v-model="formData.diabetes_discovery_year"
clearable
maxlength="50"
show-word-limit
placeholder="可填数字或文字,如:5、1年、半年"
class="w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="当地医院诊断结果" prop="local_hospital_diagnosis" 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-form-item label="口腔感觉" class="checkbox-form-item">
<el-checkbox-group v-model="formData.appetite">
<el-checkbox-button v-for="item in appetiteOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
</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-button v-for="item in waterIntakeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</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.weight_change">
<el-radio-button v-for="item in weightChangeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</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-button v-for="item in fattyLiverDegreeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="饮食情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.diet_condition">
<el-checkbox-button v-for="item in dietConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</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-button v-for="item in bodyFeelingOptions" :key="item.value" :label="item.value">
{{ item.name }}
</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-button v-for="item in sleepConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</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-button v-for="item in eyeConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</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-button v-for="item in headFeelingOptions" :key="item.value" :label="item.value">
{{ item.name }}
</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-button v-for="item in sweatConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</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-button v-for="item in skinConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</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-button v-for="item in urineConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</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-button v-for="item in stoolConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</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-button v-for="item in kidneyConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
</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" class="checkbox-form-item">
<el-checkbox-group v-model="formData.past_history">
<el-checkbox-button
v-for="item in pastHistoryOptions"
:key="item.value"
:label="item.value"
>
{{ item.name }}
</el-checkbox-button>
</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-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-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-button :label="1"></el-radio-button>
<el-radio-button :label="0"></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="家族病史">
<el-radio-group v-model="formData.family_history">
<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-button :label="1"></el-radio-button>
<el-radio-button :label="0"></el-radio-button>
</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"
:exclude-domain="true"
/>
<div class="form-tips">支持上传多张舌苔照片最多9张</div>
</el-form-item>
<el-form-item label="检查报告">
<material-picker
v-model="formData.report_files"
:limit="10"
type="image"
:exclude-domain="true"
/>
<div class="form-tips">支持上传检查报告病历彩超等影像科检查图片最多10个文件</div>
</el-form-item>
<el-form-item label="病史补充" prop="remark">
<el-input
v-mode="formData.remark"
type="ltextarea"
: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-tab-pane label="处方" name="bingli" :disabled="!formData.id" v-if="hasPermission(['tcm.diagnosis/chufang'])">
<div v-if="formData.id" class="bingli-tab-content">
<!-- 诊断信息舌苔照片检查报告 -->
<div class="bingli-diagnosis-section">
<el-divider content-position="left">诊断信息</el-divider>
<el-row :gutter="24">
<el-col :span="12">
<div class="bingli-label">舌苔照片</div>
<div v-if="formData.tongue_images?.length" class="bingli-images">
<el-image
v-for="(img, idx) in formData.tongue_images"
:key="idx"
:src="getImageUrl(img)"
:preview-src-list="(formData.tongue_images || []).map((u: string) => getImageUrl(u))"
class="bingli-img"
fit="cover"
/>
</div>
<span v-else class="bingli-empty">暂无</span>
</el-col>
<el-col :span="12">
<div class="bingli-label">检查报告</div>
<div v-if="formData.report_files?.length" class="bingli-images">
<el-image
v-for="(file, idx) in formData.report_files"
:key="idx"
:src="getImageUrl(file)"
:preview-src-list="(formData.report_files || []).map((u: string) => getImageUrl(u))"
class="bingli-img"
fit="cover"
/>
</div>
<span v-else class="bingli-empty">暂无</span>
</el-col>
</el-row>
</div>
<!-- 病历记录列表 -->
<case-record-list
ref="caseRecordListRef"
:diagnosis-id="Number(formData.id)"
@view="handleViewCase"
@openPrescription="handleOpenPrescription"
/>
</div>
<el-empty v-else description="请先保存诊单开方后病历将在此显示" />
</el-tab-pane>
<el-tab-pane
v-if="hasPermission(['tcm.diagnosis/huifang'])"
label="视频录制回放记录"
name="visit"
:disabled="!formData.id"
lazy
>
<call-record-panel
v-if="formData.id"
ref="callRecordPanelRef"
:diagnosis-id="Number(formData.id)"
/>
<el-empty v-else description="请先保存诊单后再查看视频录制回放记录" />
</el-tab-pane>
<el-tab-pane
v-if="hasPermission(['tcm.diagnosis/chat'])"
label="聊天记录"
name="chat"
:disabled="!formData.id"
lazy
>
<im-chat-record-panel
v-if="formData.id"
:diagnosis-id="Number(formData.id)"
/>
<el-empty v-else description="请先保存诊单后再查看聊天记录" />
</el-tab-pane>
<el-tab-pane
v-if="
hasPermission(['tcm.diagnosis/assign']) ||
hasPermission(['tcm.diagnosis/detail'])
"
label="指派医助记录"
name="assignLog"
:disabled="!formData.id"
lazy
>
<assign-log-panel
v-if="formData.id"
:diagnosis-id="Number(formData.id)"
/>
<el-empty v-else description="请先保存诊单后再查看指派记录" />
</el-tab-pane>
<el-tab-pane
v-if="hasPermission(['doctor.appointment/lists'])"
label="挂号记录"
name="appointmentRecords"
:disabled="!formData.id"
lazy
>
<appointment-record-panel
v-if="formData.id"
:diagnosis-id="Number(formData.id)"
/>
<el-empty v-else description="请先保存诊单后再查看挂号记录" />
</el-tab-pane>
</el-tabs>
<!-- 处方详情(查看病历) -->
<tcm-prescription ref="prescriptionRef" @success="caseRecordListRef?.refresh?.()" />
<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 { hasPermission } from '@/utils/perm'
import useUserStore from '@/stores/modules/user'
import useAppStore from '@/stores/modules/app'
import { ElMessage } from 'element-plus'
import { QuestionFilled } from '@element-plus/icons-vue'
import BloodRecordList from './components/BloodRecordList.vue'
import CaseRecordList from './components/CaseRecordList.vue'
import CallRecordPanel from './components/CallRecordPanel.vue'
import ImChatRecordPanel from './components/ImChatRecordPanel.vue'
import AssignLogPanel from './components/AssignLogPanel.vue'
import AppointmentRecordPanel from './components/AppointmentRecordPanel.vue'
import TcmPrescription from '@/components/tcm-prescription/index.vue'
import {
DIAGNOSIS_TONGUE_IMAGES_UPDATED,
type DiagnosisTongueImagesUpdatedDetail
} from '@/utils/diagnosis-sync-events'
const emit = defineEmits(['success'])
const appStore = useAppStore()
const getImageUrl = (url: string) => (url?.indexOf?.('http') === 0 ? url : appStore.getImageUrl(url || ''))
const caseRecordListRef = ref()
const callRecordPanelRef = ref<{ refresh?: () => void } | null>(null)
const prescriptionRef = ref()
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' ? '新增诊单' : '编辑诊单'))
watch([visible, activeTab], () => {
if (!visible.value) return
if (activeTab.value === 'chat' && !hasPermission(['tcm.diagnosis/chat'])) {
activeTab.value = 'basic'
}
if (activeTab.value === 'visit' && !hasPermission(['tcm.diagnosis/huifang'])) {
activeTab.value = 'basic'
}
if (
activeTab.value === 'assignLog' &&
!hasPermission(['tcm.diagnosis/assign']) &&
!hasPermission(['tcm.diagnosis/detail'])
) {
activeTab.value = 'basic'
}
if (activeTab.value === 'appointmentRecords' && !hasPermission(['doctor.appointment/lists'])) {
activeTab.value = 'basic'
}
})
function onTongueImagesSyncedFromCall(e: Event) {
if (!visible.value) return
const d = (e as CustomEvent<DiagnosisTongueImagesUpdatedDetail>).detail
if (!d?.diagnosisId || !Array.isArray(d.tongue_images)) return
if (Number(formData.value.id) !== Number(d.diagnosisId)) return
formData.value.tongue_images = [...d.tongue_images]
}
onMounted(() => {
window.addEventListener(DIAGNOSIS_TONGUE_IMAGES_UPDATED, onTongueImagesSyncedFromCall as EventListener)
})
onUnmounted(() => {
window.removeEventListener(DIAGNOSIS_TONGUE_IMAGES_UPDATED, onTongueImagesSyncedFromCall as EventListener)
})
const formData = ref({
id: '',
patient_id: '',
patient_name: '',
id_card: '',
phone: '',
gender: 1,
age: undefined as number | undefined,
diagnosis_date: '',
diagnosis_type: '',
syndrome_type: '',
diabetes_type: '',
diabetes_discovery_year: '',
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: '',
appetite: [] as string[],
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,
assistant_id: undefined as number | undefined,
tongue_images: [] as string[],
report_files: [],
symptoms: '',
tongue_coating: '',
pulse: '',
treatment_principle: '',
prescription: '',
doctor_advice: '',
remark: '',
current_medications: '',
status: 1,
external_userid: ''
})
// 保存原始完整数据
const originalPhone = ref('')
const originalIdCard = ref('')
const isPhoneFocused = ref(false)
const isIdCardFocused = ref(false)
// 脱敏函数
const maskPhone = (phone: string) => {
if (!phone || phone.length < 11) return phone
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
}
const maskIdCard = (idCard: string) => {
if (!idCard) return idCard
if (idCard.length === 15) {
return idCard.replace(/(\d{6})\d{5}(\d{4})/, '$1*****$2')
} else if (idCard.length === 18) {
return idCard.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2')
}
return idCard
}
// 手机号聚焦 - 显示完整数据
const handlePhoneFocus = () => {
isPhoneFocused.value = true
if (originalPhone.value) {
formData.value.phone = originalPhone.value
}
}
// 手机号失焦 - 恢复脱敏并验证
const handlePhoneBlur = async () => {
isPhoneFocused.value = false
// 保存原始数据并脱敏显示
if (formData.value.phone && formData.value.phone.length === 11) {
originalPhone.value = formData.value.phone
// 验证手机号唯一性
if (/^1[3-9]\d{9}$/.test(formData.value.phone)) {
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)
}
}
// 脱敏显示
formData.value.phone = maskPhone(formData.value.phone)
}
}
// 身份证聚焦 - 显示完整数据
const handleIdCardFocus = () => {
isIdCardFocused.value = true
if (originalIdCard.value) {
formData.value.id_card = originalIdCard.value
}
}
// 身份证失焦 - 恢复脱敏并验证
const handleIdCardBlur = async () => {
isIdCardFocused.value = false
// 保存原始数据并脱敏显示
if (formData.value.id_card && (formData.value.id_card.length === 15 || formData.value.id_card.length === 18)) {
originalIdCard.value = formData.value.id_card
// 验证身份证格式并计算年龄
if (/^[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)) {
// 从身份证提取出生日期计算年龄
const id = formData.value.id_card
const birthYear = parseInt(id.substring(6, 10))
const birthMonth = parseInt(id.substring(10, 12))
const birthDay = parseInt(id.substring(12, 14))
const today = new Date()
let age = today.getFullYear() - birthYear
if (today.getMonth() + 1 < birthMonth || (today.getMonth() + 1 === birthMonth && today.getDate() < birthDay)) {
age--
}
formData.value.age = age
// 验证身份证唯一性
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)
}
}
// 脱敏显示
formData.value.id_card = maskIdCard(formData.value.id_card)
}
}
// 自定义验证规则
const validatePhone = (rule: any, value: any, callback: any) => {
// 使用原始数据进行验证
const phoneToValidate = originalPhone.value || value
if (!phoneToValidate) {
callback(new Error('请输入手机号'))
return
}
if (!/^1[3-9]\d{9}$/.test(phoneToValidate)) {
callback(new Error('手机号格式不正确'))
return
}
callback()
}
const validateIdCard = (rule: any, value: any, callback: any) => {
// 使用原始数据进行验证
const idCardToValidate = originalIdCard.value || value
if (idCardToValidate && !/^[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(idCardToValidate)) {
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' }],
fasting_blood_sugar: [{ required: true, message: '请输入空腹血糖', trigger: 'blur' }],
diagnosis_type: [{ required: true, message: '请选择诊断类型', trigger: 'change' }],
diabetes_discovery_year: [{ max: 50, message: '最多50个字符', trigger: 'blur' }],
}
// 获取字典选项
const diagnosisTypeOptions = ref<any[]>([])
const syndromeTypeOptions = ref<any[]>([])
const diabetesTypeOptions = 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,
diabetesType,
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: 'diabetes_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 || []
diabetesTypeOptions.value = diabetesType?.diabetes_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 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 })
// 保存原始完整数据
originalPhone.value = data.phone || ''
originalIdCard.value = data.id_card || ''
// 显示脱敏数据
data.phone = maskPhone(data.phone || '')
data.id_card = maskIdCard(data.id_card || '')
formData.value = data
const y = data.diabetes_discovery_year
formData.value.diabetes_discovery_year =
y == null || y === '' ? '' : String(y)
} else if (type === 'add') {
const userStore = useUserStore()
formData.value.assistant_id = userStore.userInfo?.id || ''
}
}
const handleSubmit = async () => {
await formRef.value?.validate()
submitting.value = true
try {
// 准备提交数据,使用原始完整数据
const submitData = {
...formData.value,
phone: originalPhone.value || formData.value.phone,
id_card: originalIdCard.value || formData.value.id_card
}
if (mode.value === 'add') {
const result = await tcmDiagnosisAdd(submitData)
// 如果是新增,保存成功后切换到编辑模式,这样可以添加血糖血压记录
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
// 更新原始数据
originalPhone.value = detail.phone || ''
originalIdCard.value = detail.id_card || ''
// 显示脱敏数据
formData.value.phone = maskPhone(detail.phone || '')
formData.value.id_card = maskIdCard(detail.id_card || '')
} catch (error) {
console.error('获取详情失败:', error)
}
}
} else {
await tcmDiagnosisEdit(submitData)
}
emit('success')
} catch (error) {
console.error('提交失败:', error)
} finally {
submitting.value = false
}
}
const handleViewCase = (row: any) => {
prescriptionRef.value?.openById(row.id)
}
// 开方:传入当前诊单数据,包含详细病历(深拷贝避免响应式引用)
const handleOpenPrescription = () => {
const data = {
id: formData.value.id,
diagnosis_id: formData.value.id,
appointment_id: 0,
patient_name: formData.value.patient_name,
patient_phone: formData.value.phone,
patient_gender: formData.value.gender,
patient_age: formData.value.age,
case_record: JSON.parse(JSON.stringify(formData.value))
}
prescriptionRef.value?.open(data)
}
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: '',
diabetes_type: '',
diabetes_discovery_year: '',
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: '',
appetite: [] as string[],
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,
assistant_id: undefined as number | undefined,
tongue_images: [] as string[],
report_files: [],
symptoms: '',
tongue_coating: '',
pulse: '',
treatment_principle: '',
prescription: '',
doctor_advice: '',
remark: '',
current_medications: '',
status: 1,
external_userid: ''
}
// 重置原始数据
originalPhone.value = ''
originalIdCard.value = ''
isPhoneFocused.value = false
isIdCardFocused.value = false
visible.value = false
}
defineExpose({
open,
refreshCaseList: () => caseRecordListRef.value?.refresh?.()
})
</script>
<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: 13px;
font-weight: 600;
color: #303133;
background: #f5f7fa;
padding: 0 16px;
}
}
:deep(.el-form-item) {
margin-bottom: 18px;
}
: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 {
:deep(.el-radio-group) {
display: flex;
flex-wrap: wrap;
gap: 12px;
.el-radio {
margin-right: 0;
margin-bottom: 0;
}
}
}
// 复选框表单项
.checkbox-form-item {
:deep(.el-checkbox-group) {
display: flex;
flex-wrap: wrap;
gap: 12px 16px;
.el-checkbox {
margin-right: 0;
margin-bottom: 0;
}
}
}
// 通用单选框样式
:deep(.el-radio-group) {
.el-radio {
margin-right: 16px;
margin-bottom: 8px;
&:last-child {
margin-right: 0;
}
}
}
// 通用复选框样式
:deep(.el-checkbox-group) {
.el-checkbox {
margin-right: 16px;
margin-bottom: 8px;
}
}
.form-tips {
font-size: 12px;
color: #909399;
margin-top: 4px;
line-height: 1.5;
}
/* 病历 tab 诊断信息 */
.bingli-tab-content {
padding: 0 4px;
}
.bingli-diagnosis-section {
margin-bottom: 24px;
padding: 16px;
background: #f5f7fa;
border-radius: 6px;
}
.bingli-label {
font-size: 13px;
font-weight: 500;
color: #606266;
margin-bottom: 10px;
}
.bingli-images {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.bingli-img {
width: 80px;
height: 80px;
border-radius: 4px;
cursor: pointer;
}
.bingli-empty {
font-size: 13px;
color: #909399;
}
// 输入框样式优化
:deep(.el-input__inner),
:deep(.el-textarea__inner) {
border-radius: 4px;
}
// 选择器样式优化
:deep(.el-select) {
.el-input__inner {
border-radius: 4px;
}
}
// 日期选择器样式优化
:deep(.el-date-editor) {
.el-input__inner {
border-radius: 4px;
}
}
}
</style>
<style lang="scss" scoped>
/* 全局样式,用于提高弹出层的z-index */
.high-z-index {
z-index: 3000 !important;
}
/* 降低抽屉及其遮罩层的z-index */
.el-drawer {
z-index: 1500 !important;
}
.el-overlay {
z-index: 1499 ;
}
/* 确保日期选择器和下拉选择器的弹出层在最上层 */
.el-picker__popper,
.el-select__popper,
.el-popper {
z-index: 3000 !important;
}
/* 确保Element Plus的弹出层容器在最上层 */
.el-picker-panel,
.el-select-dropdown {
z-index: 3000 !important;
}
</style>