This commit is contained in:
Your Name
2026-03-20 13:56:40 +08:00
parent 7147c8e148
commit d765517b74
258 changed files with 2481 additions and 364 deletions
+356 -4
View File
@@ -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>
+4 -1
View File
@@ -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,
+21
View File
@@ -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,
+19 -11
View File
@@ -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()
})
+22 -5
View File
@@ -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 = {
+21 -1
View File
@@ -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) {