更新
This commit is contained in:
@@ -159,6 +159,7 @@
|
||||
</div>
|
||||
|
||||
<div v-else class="prescription-preview">
|
||||
<div class="prescription-preview-content">
|
||||
<div ref="prescriptionPrintRef" class="prescription-print">
|
||||
<div class="prescription-header">
|
||||
<h2 class="prescription-title">{{ templateConfig.stationName }}处方笺</h2>
|
||||
@@ -237,6 +238,106 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详细病历 - 单独A3纸张 -->
|
||||
<div v-if="savedPrescription.case_record" ref="caseRecordPrintRef" class="case-record-a3">
|
||||
<div class="case-record-a3-inner">
|
||||
<h2 class="case-record-a3-title">{{ templateConfig.stationName }} 详细病历</h2>
|
||||
<div class="case-record-a3-body">
|
||||
<!-- 基本信息 -->
|
||||
<div class="cr-section">
|
||||
<div class="cr-section-title">基本信息</div>
|
||||
<div class="cr-grid">
|
||||
<div class="cr-item"><span class="cr-label">诊单ID</span>{{ savedPrescription.case_record.patient_id || '—' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">姓名</span>{{ savedPrescription.case_record.patient_name || '—' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">身份证号</span>{{ savedPrescription.case_record.id_card || '—' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">手机号</span>{{ savedPrescription.case_record.phone || '—' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">性别</span>{{ savedPrescription.case_record.gender === 1 ? '男' : '女' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">年龄</span>{{ savedPrescription.case_record.age ?? '—' }}岁</div>
|
||||
<div class="cr-item"><span class="cr-label">婚姻状态</span>{{ ['未婚','已婚','离异'][savedPrescription.case_record.marital_status] ?? '—' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">身高</span>{{ savedPrescription.case_record.height ?? '—' }}cm</div>
|
||||
<div class="cr-item"><span class="cr-label">体重</span>{{ savedPrescription.case_record.weight ?? '—' }}kg</div>
|
||||
<div class="cr-item"><span class="cr-label">地区</span>{{ savedPrescription.case_record.region || '—' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 生命体征 -->
|
||||
<div class="cr-section">
|
||||
<div class="cr-section-title">生命体征</div>
|
||||
<div class="cr-grid">
|
||||
<div class="cr-item"><span class="cr-label">血压</span>{{ (savedPrescription.case_record.systolic_pressure != null && savedPrescription.case_record.diastolic_pressure != null) ? (savedPrescription.case_record.systolic_pressure + '/' + savedPrescription.case_record.diastolic_pressure + ' mmHg') : '—' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">空腹血糖</span>{{ savedPrescription.case_record.fasting_blood_sugar != null ? savedPrescription.case_record.fasting_blood_sugar + ' mmol/L' : '—' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 主诉 -->
|
||||
<div class="cr-section">
|
||||
<div class="cr-section-title">主诉</div>
|
||||
<div class="cr-grid">
|
||||
<div class="cr-item"><span class="cr-label">诊断日期</span>{{ savedPrescription.case_record.diagnosis_date || '—' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">诊断类型</span>{{ getDictLabel(diagnosisTypeOptions, savedPrescription.case_record.diagnosis_type) || '—' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">证型</span>{{ getDictLabel(syndromeTypeOptions, savedPrescription.case_record.syndrome_type) || '—' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">糖尿病期数</span>{{ getDictLabel(diabetesTypeOptions, savedPrescription.case_record.diabetes_type) || '—' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">发现糖尿病患病史</span>{{ savedPrescription.case_record.diabetes_discovery_year != null ? savedPrescription.case_record.diabetes_discovery_year + ' 年' : '—' }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">当地医院诊断</span>{{ formatLocalDiagnosis(savedPrescription.case_record.local_hospital_diagnosis) }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">当地就诊医院</span>{{ savedPrescription.case_record.local_hospital_name || '—' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 现病史 -->
|
||||
<div class="cr-section">
|
||||
<div class="cr-section-title">现病史</div>
|
||||
<div class="cr-grid">
|
||||
<div class="cr-item cr-full"><span class="cr-label">口腔感觉</span>{{ getMultiLabels(appetiteOptions, savedPrescription.case_record.appetite) }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">每日饮水量</span>{{ getDictLabel(waterIntakeOptions, savedPrescription.case_record.water_intake) || '—' }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">体重变化</span>{{ getDictLabel(weightChangeOptions, savedPrescription.case_record.weight_change) || '—' }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">脂肪肝程度</span>{{ getDictLabel(fattyLiverDegreeOptions, savedPrescription.case_record.fatty_liver_degree) || '—' }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">饮食情况</span>{{ getMultiLabels(dietConditionOptions, savedPrescription.case_record.diet_condition) }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">肢体感觉</span>{{ getMultiLabels(bodyFeelingOptions, savedPrescription.case_record.body_feeling) }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">睡眠情况</span>{{ getMultiLabels(sleepConditionOptions, savedPrescription.case_record.sleep_condition) }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">眼睛情况</span>{{ getMultiLabels(eyeConditionOptions, savedPrescription.case_record.eye_condition) }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">头部感觉</span>{{ getMultiLabels(headFeelingOptions, savedPrescription.case_record.head_feeling) }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">出汗情况</span>{{ getMultiLabels(sweatConditionOptions, savedPrescription.case_record.sweat_condition) }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">皮肤情况</span>{{ getMultiLabels(skinConditionOptions, savedPrescription.case_record.skin_condition) }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">小便情况</span>{{ getMultiLabels(urineConditionOptions, savedPrescription.case_record.urine_condition) }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">大便情况</span>{{ getMultiLabels(stoolConditionOptions, savedPrescription.case_record.stool_condition) }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">腰肾情况</span>{{ getMultiLabels(kidneyConditionOptions, savedPrescription.case_record.kidney_condition) }}</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label">其他补充</span>{{ savedPrescription.case_record.symptoms || '—' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 既往史 -->
|
||||
<div class="cr-section">
|
||||
<div class="cr-section-title">既往史</div>
|
||||
<div class="cr-item cr-full"><span class="cr-label"></span>{{ getMultiLabels(pastHistoryOptions, savedPrescription.case_record.past_history) }}</div>
|
||||
</div>
|
||||
<!-- 其他病史 -->
|
||||
<div class="cr-section">
|
||||
<div class="cr-section-title">其他病史</div>
|
||||
<div class="cr-grid">
|
||||
<div class="cr-item"><span class="cr-label">外伤史</span>{{ savedPrescription.case_record.trauma_history === 1 ? '有' : '无' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">手术史</span>{{ savedPrescription.case_record.surgery_history === 1 ? '有' : '无' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">过敏史</span>{{ savedPrescription.case_record.allergy_history === 1 ? '有' : '无' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">家族病史</span>{{ savedPrescription.case_record.family_history === 1 ? '有' : '无' }}</div>
|
||||
<div class="cr-item"><span class="cr-label">妊娠哺乳史</span>{{ savedPrescription.case_record.pregnancy_history === 1 ? '有' : '无' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 诊断信息 -->
|
||||
<div class="cr-section">
|
||||
<div class="cr-section-title">诊断信息</div>
|
||||
<div class="cr-grid" v-if="savedPrescription.case_record.tongue_images?.length">
|
||||
<div class="cr-item cr-full">
|
||||
<span class="cr-label">舌苔照片</span>
|
||||
<div class="cr-images">
|
||||
<img v-for="(img, idx) in savedPrescription.case_record.tongue_images" :key="idx" :src="getImageUrl(img)" class="cr-img" crossorigin="anonymous" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cr-grid">
|
||||
<div class="cr-item cr-full"><span class="cr-label">舌象</span>{{ savedPrescription.case_record.tongue_coating || '—' }}</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-4">
|
||||
<el-button @click="handleDownloadImage">
|
||||
<el-icon><Download /></el-icon>
|
||||
@@ -271,11 +372,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import feedback from '@/utils/feedback'
|
||||
import { prescriptionAdd, prescriptionDetail, prescriptionGetByAppointment, prescriptionVoid } from '@/api/tcm'
|
||||
import { prescriptionAdd, prescriptionDetail, prescriptionGetByAppointment, prescriptionVoid, tcmDiagnosisDetail } from '@/api/tcm'
|
||||
import { getDictData } from '@/api/app'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { jsPDF } from 'jspdf'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import useAppStore from '@/stores/modules/app'
|
||||
import { Download, Document } from '@element-plus/icons-vue'
|
||||
|
||||
interface Herb {
|
||||
@@ -296,6 +398,7 @@ interface PrescriptionForm {
|
||||
pulse: string
|
||||
tongue: string
|
||||
clinical_diagnosis: string
|
||||
case_record: any
|
||||
herbs: Herb[]
|
||||
dose_count: number
|
||||
dose_unit: string
|
||||
@@ -312,6 +415,7 @@ const formRef = ref()
|
||||
const saving = ref(false)
|
||||
const voiding = ref(false)
|
||||
const prescriptionPrintRef = ref<HTMLElement>()
|
||||
const caseRecordPrintRef = ref<HTMLElement>()
|
||||
const signatureCanvasRef = ref<HTMLCanvasElement>()
|
||||
const isDrawing = ref(false)
|
||||
const savedPrescription = ref<any>(null)
|
||||
@@ -335,6 +439,7 @@ const formData = reactive<PrescriptionForm>({
|
||||
pulse: '',
|
||||
tongue: '',
|
||||
clinical_diagnosis: '',
|
||||
case_record: null as any,
|
||||
herbs: [],
|
||||
dose_count: 1,
|
||||
dose_unit: '剂',
|
||||
@@ -345,11 +450,32 @@ const formData = reactive<PrescriptionForm>({
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
const appStore = useAppStore()
|
||||
const getImageUrl = (url: any) => {
|
||||
const s = typeof url === 'string' ? url : (url?.url ?? url?.uri ?? url?.path ?? '')
|
||||
return s ? (String(s).startsWith('http') ? s : appStore.getImageUrl(s)) : ''
|
||||
}
|
||||
|
||||
const doseUnitOptions = ['剂', '丸', '袋', '盒', '瓶', '膏', '贴', '包', '片', '粒']
|
||||
|
||||
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 getDictLabel = (options: any[], value: string) => {
|
||||
if (!value) return ''
|
||||
@@ -359,15 +485,68 @@ const getDictLabel = (options: any[], value: string) => {
|
||||
|
||||
const loadDictOptions = async () => {
|
||||
try {
|
||||
const [dt, st] = await Promise.all([
|
||||
const [
|
||||
dt, st, 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: '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 = dt?.diagnosis_type || []
|
||||
syndromeTypeOptions.value = st?.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 (_) {}
|
||||
}
|
||||
|
||||
const getMultiLabels = (options: any[], values: string[] | string | undefined) => {
|
||||
if (!values || (Array.isArray(values) && values.length === 0)) return '—'
|
||||
const arr = Array.isArray(values) ? values : (typeof values === 'string' ? values.split(',') : [])
|
||||
const names = arr.map(v => {
|
||||
const item = options.find((o: any) => o.value === v)
|
||||
return item ? item.name : v
|
||||
}).filter(Boolean)
|
||||
return names.length ? names.join('、') : '—'
|
||||
}
|
||||
|
||||
const formatLocalDiagnosis = (val: string[] | string | undefined) => {
|
||||
if (!val || (Array.isArray(val) && val.length === 0)) return '—'
|
||||
const arr = Array.isArray(val) ? val : (typeof val === 'string' ? val.split(',') : [])
|
||||
return arr.filter(Boolean).join('、') || '—'
|
||||
}
|
||||
|
||||
const buildClinicalDiagnosis = (diagnosis: any) => {
|
||||
if (diagnosis?.symptoms?.trim()) return diagnosis.symptoms.trim()
|
||||
const dtLabel = getDictLabel(diagnosisTypeOptions.value, diagnosis?.diagnosis_type)
|
||||
@@ -401,6 +580,17 @@ const open = async (data: any, options?: { templateId?: number; stationName?: st
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// 获取详细诊单作为病历快照(每次开方保存一份)
|
||||
let caseRecord = data.case_record && Object.keys(data.case_record).length > 0 ? data.case_record : null
|
||||
if (!caseRecord && diagnosisId) {
|
||||
try {
|
||||
const res = await tcmDiagnosisDetail({ id: Number(diagnosisId) })
|
||||
caseRecord = res && typeof res === 'object' ? res : null
|
||||
} catch (e) {
|
||||
console.warn('获取诊单详情失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
formData.diagnosis_id = Number(diagnosisId)
|
||||
formData.appointment_id = appointmentId
|
||||
formData.patient_name = data.patient_name || diagnosis.patient_name || ''
|
||||
@@ -413,6 +603,7 @@ const open = async (data: any, options?: { templateId?: number; stationName?: st
|
||||
formData.pulse = diagnosis.pulse || ''
|
||||
formData.tongue = diagnosis.tongue_coating || diagnosis.tongue || ''
|
||||
formData.clinical_diagnosis = buildClinicalDiagnosis(diagnosis)
|
||||
formData.case_record = caseRecord
|
||||
formData.herbs = []
|
||||
formData.dose_count = 1
|
||||
formData.dose_unit = '剂'
|
||||
@@ -430,6 +621,9 @@ const openById = async (prescriptionId: number) => {
|
||||
try {
|
||||
const res = await prescriptionDetail({ id: prescriptionId })
|
||||
savedPrescription.value = res
|
||||
if (res?.case_record && Object.keys(res.case_record).length > 0) {
|
||||
await loadDictOptions()
|
||||
}
|
||||
visible.value = true
|
||||
} catch (e) {
|
||||
feedback.msgError('加载处方失败')
|
||||
@@ -560,6 +754,7 @@ const handleSave = async () => {
|
||||
pulse: formData.pulse,
|
||||
tongue: formData.tongue,
|
||||
clinical_diagnosis: formData.clinical_diagnosis,
|
||||
case_record: formData.case_record,
|
||||
herbs: formData.herbs,
|
||||
dose_count: formData.dose_count,
|
||||
dose_unit: formData.dose_unit || '剂',
|
||||
@@ -607,7 +802,18 @@ const handleVoid = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleNewPrescription = () => {
|
||||
const fetchCaseRecord = async (diagnosisId: number) => {
|
||||
if (!diagnosisId) return null
|
||||
try {
|
||||
const res = await tcmDiagnosisDetail({ id: diagnosisId })
|
||||
return res && typeof res === 'object' ? res : null
|
||||
} catch (e) {
|
||||
console.warn('获取诊单详情失败:', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const handleNewPrescription = async () => {
|
||||
const saved = savedPrescription.value
|
||||
if (saved) {
|
||||
// 将患者信息、诊断信息填入表单,新建处方时保留
|
||||
@@ -630,6 +836,10 @@ const handleNewPrescription = () => {
|
||||
formData.doctor_name = userStore.userInfo?.name || saved.doctor_name || ''
|
||||
formData.doctor_signature = ''
|
||||
formData.herbs = []
|
||||
// 新建处方时重新获取病历(旧处方可能没有 case_record)
|
||||
formData.case_record = saved.case_record && Object.keys(saved.case_record).length > 0
|
||||
? saved.case_record
|
||||
: await fetchCaseRecord(Number(formData.diagnosis_id))
|
||||
setTimeout(() => initSignatureCanvas(), 100)
|
||||
}
|
||||
savedPrescription.value = null
|
||||
@@ -646,6 +856,42 @@ const captureElement = async (): Promise<HTMLCanvasElement> => {
|
||||
})
|
||||
}
|
||||
|
||||
/** 将元素内图片转为 base64,确保 html2canvas 能正确绘制 */
|
||||
const preloadImagesToDataUrl = async (el: HTMLElement): Promise<void> => {
|
||||
const imgs = el.querySelectorAll<HTMLImageElement>('img[src^="http"]')
|
||||
const tasks = Array.from(imgs).map(async (img) => {
|
||||
try {
|
||||
const res = await fetch(img.src, { mode: 'cors' })
|
||||
if (!res.ok) return
|
||||
const blob = await res.blob()
|
||||
const dataUrl = await new Promise<string>((resolve, reject) => {
|
||||
const r = new FileReader()
|
||||
r.onload = () => resolve(r.result as string)
|
||||
r.onerror = reject
|
||||
r.readAsDataURL(blob)
|
||||
})
|
||||
img.src = dataUrl
|
||||
} catch {
|
||||
// 跨域失败时保留原 src,依赖 useCORS
|
||||
}
|
||||
})
|
||||
await Promise.allSettled(tasks)
|
||||
}
|
||||
|
||||
const captureCaseRecordElement = async (): Promise<HTMLCanvasElement | null> => {
|
||||
const el = caseRecordPrintRef.value
|
||||
if (!el) return null
|
||||
el.scrollIntoView({ behavior: 'instant', block: 'nearest' })
|
||||
await preloadImagesToDataUrl(el)
|
||||
await new Promise((r) => setTimeout(r, 200))
|
||||
return html2canvas(el, {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
logging: false,
|
||||
backgroundColor: '#ffffff'
|
||||
})
|
||||
}
|
||||
|
||||
const handleDownloadImage = async () => {
|
||||
try {
|
||||
const canvas = await captureElement()
|
||||
@@ -674,6 +920,19 @@ const handleDownloadPdf = async () => {
|
||||
const imgW = canvas.width * scale
|
||||
const imgH = canvas.height * scale
|
||||
pdf.addImage(imgData, 'PNG', 0, 0, imgW, imgH)
|
||||
|
||||
// 若有详细病历,单独一页A3
|
||||
const caseCanvas = await captureCaseRecordElement()
|
||||
if (caseCanvas) {
|
||||
pdf.addPage('a3', 'p')
|
||||
const a3W = 297
|
||||
const a3H = 420
|
||||
const caseScale = Math.min(a3W / caseCanvas.width, a3H / caseCanvas.height)
|
||||
const caseImgW = caseCanvas.width * caseScale
|
||||
const caseImgH = caseCanvas.height * caseScale
|
||||
pdf.addImage(caseCanvas.toDataURL('image/png'), 'PNG', 0, 0, caseImgW, caseImgH)
|
||||
}
|
||||
|
||||
pdf.save(`处方_${savedPrescription.value?.patient_name || '未命名'}_${Date.now()}.pdf`)
|
||||
feedback.msgSuccess('PDF已下载')
|
||||
} catch (e) {
|
||||
@@ -851,6 +1110,7 @@ defineExpose({
|
||||
margin: 12px 0;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.prescription-area {
|
||||
border-top: 1px dashed #ddd;
|
||||
padding-top: 12px;
|
||||
@@ -893,4 +1153,96 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.prescription-preview-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* 详细病历 - 单独A3纸张 */
|
||||
.case-record-a3 {
|
||||
width: 297mm;
|
||||
min-height: 420mm;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #dcdfe6;
|
||||
page-break-before: always;
|
||||
|
||||
.case-record-a3-inner {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.case-record-a3-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin: 0 0 20px 0;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
|
||||
.case-record-a3-body {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cr-section {
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cr-section-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.cr-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px 16px;
|
||||
}
|
||||
|
||||
.cr-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
line-height: 1.6;
|
||||
|
||||
&.cr-full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.cr-label {
|
||||
flex-shrink: 0;
|
||||
color: #909399;
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.cr-images {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.cr-img {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
object-fit: cover;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,7 +21,10 @@ const useAppStore = defineStore({
|
||||
},
|
||||
actions: {
|
||||
getImageUrl(url: string) {
|
||||
return url.indexOf('http') ? `${this.config.oss_domain}${url}` : url
|
||||
if (!url || typeof url !== 'string') return ''
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) return url
|
||||
const domain = this.config.oss_domain || (typeof window !== 'undefined' ? window.location.origin + '/' : '')
|
||||
return domain ? `${domain.replace(/\/$/, '')}${url.startsWith('/') ? url : '/' + url}` : url
|
||||
},
|
||||
getConfig() {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -308,6 +308,15 @@ const formRules = reactive({
|
||||
trigger: ['change']
|
||||
}
|
||||
],
|
||||
dept_id: [
|
||||
{
|
||||
required: true,
|
||||
type: 'array',
|
||||
min: 1,
|
||||
message: '请选择归属部门',
|
||||
trigger: ['change']
|
||||
}
|
||||
],
|
||||
phone: [
|
||||
{
|
||||
validator: phoneValidator,
|
||||
|
||||
@@ -105,6 +105,16 @@
|
||||
<el-input v-model="formData.license_no" placeholder="请输入执业证书编号" clearable />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 医生资质图片 -->
|
||||
<el-form-item label="资质图片">
|
||||
<div>
|
||||
<div>
|
||||
<material-picker v-model="formData.qualification_images" :limit="9" />
|
||||
</div>
|
||||
<div class="form-tips">上传医生资质证书、荣誉证书等图片,最多9张,建议尺寸:800*600px</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 图文问诊 -->
|
||||
<el-form-item label="图文问诊">
|
||||
<el-switch
|
||||
@@ -273,6 +283,7 @@ const formData = reactive({
|
||||
avatar: '',
|
||||
title: '', // 职称
|
||||
license_no: '', // 执业证书编号
|
||||
qualification_images: '', // 医生资质图片
|
||||
enable_image_consult: 1, // 是否开启图文问诊
|
||||
enable_video_consult: 1, // 是否开启视频问诊
|
||||
enable_charge: 0, // 是否开启收费
|
||||
@@ -340,6 +351,15 @@ const formRules = reactive({
|
||||
trigger: ['change']
|
||||
}
|
||||
],
|
||||
dept_id: [
|
||||
{
|
||||
required: true,
|
||||
type: 'array',
|
||||
min: 1,
|
||||
message: '请选择归属部门',
|
||||
trigger: ['change']
|
||||
}
|
||||
],
|
||||
phone: [
|
||||
{
|
||||
validator: phoneValidator,
|
||||
@@ -403,6 +423,7 @@ const open = (type = 'add') => {
|
||||
avatar: '',
|
||||
title: '',
|
||||
license_no: '',
|
||||
qualification_images: '',
|
||||
enable_image_consult: 1,
|
||||
enable_video_consult: 1,
|
||||
enable_charge: 0,
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
<el-table-column label="操作" width="380" fixed="right" align="left">
|
||||
<template #default="{ row }">
|
||||
<div class="action-btns">
|
||||
<el-button
|
||||
<!-- <el-button
|
||||
v-perms="['doctor.appointment/detail']"
|
||||
type="primary"
|
||||
link
|
||||
@@ -185,17 +185,17 @@
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
</el-button> -->
|
||||
<el-button
|
||||
v-perms="['tcm.diagnosis/kaifang']"
|
||||
v-perms="['tcm.diagnosis/edit']"
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="handleViewCase(row)"
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
病历
|
||||
编辑患者
|
||||
</el-button>
|
||||
<template v-if="row.status === 1">
|
||||
|
||||
<el-button
|
||||
v-perms="['tcm.diagnosis/videoQr']"
|
||||
type="warning"
|
||||
@@ -234,7 +234,7 @@
|
||||
>
|
||||
开方
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<el-dropdown trigger="click" @command="(cmd) => handleAction(cmd, row)" placement="bottom-end">
|
||||
<el-button type="info" link size="small">
|
||||
更多
|
||||
@@ -243,11 +243,11 @@
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-perms="['tcm.diagnosis/edit']"
|
||||
command="edit"
|
||||
v-perms="['tcm.diagnosis/kaifang']"
|
||||
command="viewCase"
|
||||
>
|
||||
<el-icon><Edit /></el-icon>
|
||||
编辑患者
|
||||
<el-icon><Document /></el-icon>
|
||||
病历
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="row.status === 1"
|
||||
@@ -761,6 +761,9 @@ const handleAction = (command: string, row: any) => {
|
||||
case 'edit':
|
||||
handleEditPatient(row)
|
||||
break
|
||||
case 'viewCase':
|
||||
handleViewCase(row)
|
||||
break
|
||||
case 'complete':
|
||||
handleComplete(row)
|
||||
break
|
||||
@@ -832,6 +835,11 @@ const handleEditPatient = (row: any) => {
|
||||
editRef.value?.open('edit', row.patient_id)
|
||||
}
|
||||
|
||||
// 编辑患者(按钮直接调用)
|
||||
const handleEdit = (row: any) => {
|
||||
handleEditPatient(row)
|
||||
}
|
||||
|
||||
// 聊天
|
||||
const handleChat = async (row: any) => {
|
||||
if (!row.patient_id) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="case-record-list">
|
||||
<div class="mb-3 flex justify-end">
|
||||
<el-button type="primary" size="small" @click="handleOpenPrescription">开方</el-button>
|
||||
</div>
|
||||
<el-table :data="caseList" border v-loading="loading">
|
||||
<el-table-column prop="prescription_date" label="就诊日期" width="120" />
|
||||
<el-table-column prop="visit_no" label="门诊号" width="120" />
|
||||
@@ -46,7 +49,7 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['view'])
|
||||
const emit = defineEmits(['view', 'openPrescription'])
|
||||
|
||||
const caseList = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
@@ -75,6 +78,10 @@ const handleView = (row: any) => {
|
||||
emit('view', row)
|
||||
}
|
||||
|
||||
const handleOpenPrescription = () => {
|
||||
emit('openPrescription')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCases()
|
||||
})
|
||||
|
||||
@@ -225,7 +225,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="发现糖尿病病患病史" prop="diabetes_discovery_year">
|
||||
<el-form-item label="发现糖尿病患病史" prop="diabetes_discovery_year">
|
||||
<el-input-number
|
||||
v-model="formData.diabetes_discovery_year"
|
||||
:min="0"
|
||||
@@ -241,8 +241,8 @@
|
||||
<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-button label="消渴病">消渴病</el-checkbox-button>
|
||||
<el-checkbox-button label="糖尿病前期">糖尿病前期</el-checkbox-button>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
|
||||
@@ -519,12 +519,12 @@
|
||||
/>
|
||||
</el-form-item> -->
|
||||
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-form-item label="病史补充" prop="remark">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入备注"
|
||||
placeholder="请输入病史补充"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -581,6 +581,7 @@
|
||||
ref="caseRecordListRef"
|
||||
:diagnosis-id="Number(formData.id)"
|
||||
@view="handleViewCase"
|
||||
@openPrescription="handleOpenPrescription"
|
||||
/>
|
||||
</div>
|
||||
<el-empty v-else description="请先保存诊单,开方后病历将在此显示" />
|
||||
@@ -715,6 +716,7 @@ const formRules = {
|
||||
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_date: [{ required: true, message: '请选择诊断日期', trigger: 'change' }],
|
||||
diagnosis_type: [{ required: true, message: '请选择诊断类型', trigger: 'change' }],
|
||||
diabetes_discovery_year: [{ required: true, message: '请输入发现糖尿病病患病史年数', trigger: 'blur' }],
|
||||
@@ -914,6 +916,21 @@ 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 = {
|
||||
|
||||
@@ -161,6 +161,7 @@
|
||||
<el-dropdown-item v-if="hasPermission(['tcm.diagnosis/assign'])" command="assign"><el-icon><User /></el-icon>指派</el-dropdown-item>
|
||||
<el-dropdown-item v-if="row.has_appointment && hasPermission(['tcm.diagnosis/videoQr'])" command="videoQr"><el-icon><Picture /></el-icon>视频二维码</el-dropdown-item>
|
||||
<el-dropdown-item v-if="hasPermission(['tcm.diagnosis/guahao'])" command="confirmQr"><el-icon><Picture /></el-icon>二维码</el-dropdown-item>
|
||||
<el-dropdown-item v-if="row.has_appointment && hasPermission(['tcm.diagnosis/guahao'])" command="cancelApt"><el-icon><CircleClose /></el-icon>取消挂号</el-dropdown-item>
|
||||
<el-dropdown-item command="order" v-if="hasPermission(['tcm.diagnosis/order'])"><el-icon><Document /></el-icon>创建订单</el-dropdown-item>
|
||||
<el-dropdown-item v-if="hasPermission(['tcm.diagnosis/delete'])" command="delete" divided><el-icon><Delete /></el-icon><span class="text-danger">删除</span></el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
@@ -454,12 +455,13 @@
|
||||
<script setup lang="ts" name="tcmDiagnosis">
|
||||
import { tcmDiagnosisLists, tcmDiagnosisDelete, tcmDiagnosisAssign, getAssistants, generateMiniProgramQrcode, generateOrderQrcode, getWechatChatRecords, addWechatChatRecord, deleteWechatChatRecord, getWechatExternalContact, fillIdCard } from '@/api/tcm'
|
||||
import { orderCreate } from '@/api/order'
|
||||
import { cancelAppointment } from '@/api/doctor'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import { getDictData } from '@/api/app'
|
||||
import { getWeappConfig } from '@/api/channel/weapp'
|
||||
import feedback from '@/utils/feedback'
|
||||
import { hasPermission } from '@/utils/perm'
|
||||
import { Loading, Warning, List, CircleCheck, Clock, ArrowDown, RefreshRight, Picture, User, Document, Delete } from '@element-plus/icons-vue'
|
||||
import { Loading, Warning, List, CircleCheck, Clock, ArrowDown, RefreshRight, Picture, User, Document, Delete, CircleClose } from '@element-plus/icons-vue'
|
||||
import EditPopup from './edit.vue'
|
||||
import DetailPopup from './detail.vue'
|
||||
import AppointmentPopup from './appointment.vue'
|
||||
@@ -862,11 +864,29 @@ const handleRowAction = (cmd: string, row: any) => {
|
||||
handleVideoQRCode(row)
|
||||
break
|
||||
case 'confirmQr': handleMiniProgramQRCode(row); break
|
||||
case 'cancelApt': handleCancelAppointment(row); break
|
||||
case 'order': handleCreateOrder(row); break
|
||||
case 'delete': handleDelete(row.id); break
|
||||
}
|
||||
}
|
||||
|
||||
// 取消挂号
|
||||
const handleCancelAppointment = async (row: any) => {
|
||||
if (!row.has_appointment || !row.appointment_id) {
|
||||
feedback.msgWarning('该诊单未挂号或挂号信息异常')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await feedback.confirm(`确定要取消患者「${row.patient_name}」的挂号吗?`)
|
||||
await cancelAppointment({ id: row.appointment_id })
|
||||
feedback.msgSuccess('取消挂号成功')
|
||||
getLists()
|
||||
fetchDateCounts()
|
||||
} catch (e: any) {
|
||||
if (e !== 'cancel') feedback.msgError(e?.msg || '取消挂号失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 患者挂号
|
||||
const handleAppointment = (row: any) => {
|
||||
if (!row.patient_id) {
|
||||
|
||||
Reference in New Issue
Block a user