1249 lines
50 KiB
Vue
1249 lines
50 KiB
Vue
<template>
|
||
<el-drawer
|
||
v-model="visible"
|
||
title="中医处方单"
|
||
size="900px"
|
||
:close-on-click-modal="false"
|
||
@close="handleClose"
|
||
>
|
||
<div v-if="!savedPrescription" class="prescription-edit">
|
||
<el-form ref="formRef" :model="formData" label-width="100px" class="prescription-form">
|
||
<div class="info-section">
|
||
<div class="section-title">患者信息</div>
|
||
<el-row :gutter="16">
|
||
<el-col :span="8">
|
||
<el-form-item label="姓名">
|
||
<span>{{ formData.patient_name }}</span>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="性别">
|
||
<span>{{ formData.gender_desc }}</span>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="年龄">
|
||
<span>{{ formData.age }}</span>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="电话">
|
||
<span>{{ formData.phone }}</span>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="门诊号">
|
||
<el-input v-model="formData.visit_no" placeholder="门诊号" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
|
||
<div class="info-section">
|
||
<div class="section-title">诊断信息</div>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="面象" prop="pulse">
|
||
<el-input v-model="formData.pulse" placeholder="面象" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="舌象" prop="tongue">
|
||
<el-input v-model="formData.tongue" placeholder="舌象" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="24">
|
||
<el-form-item label="临床诊断" prop="clinical_diagnosis">
|
||
<el-input
|
||
v-model="formData.clinical_diagnosis"
|
||
type="textarea"
|
||
:rows="2"
|
||
placeholder="请输入临床诊断"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
|
||
<div class="info-section">
|
||
<div class="section-title">
|
||
中药处方 (RP)
|
||
<el-button type="primary" size="small" @click="handleAddHerb">添加中药</el-button>
|
||
</div>
|
||
<div class="mb-3">
|
||
<el-form-item label="处方价格" prop="amount" class="!mb-0">
|
||
<el-input-number
|
||
v-model="formData.amount"
|
||
:min="0"
|
||
:precision="2"
|
||
placeholder="请输入处方总价(元)"
|
||
class="!w-40"
|
||
/>
|
||
<span class="ml-2 text-gray-500">元</span>
|
||
</el-form-item>
|
||
</div>
|
||
<div v-for="(herb, index) in formData.herbs" :key="index" class="herb-item">
|
||
<el-form-item :label="`中药${index + 1}`">
|
||
<div class="flex gap-2 items-center flex-wrap">
|
||
<el-input
|
||
v-model="herb.name"
|
||
placeholder="药品名称"
|
||
style="width: 160px"
|
||
/>
|
||
<el-input-number
|
||
v-model="herb.dosage"
|
||
:min="0.1"
|
||
:step="1"
|
||
:precision="1"
|
||
placeholder="剂量(克)"
|
||
style="width: 120px"
|
||
/>
|
||
<span class="text-gray-500">克</span>
|
||
<el-button type="danger" size="small" link @click="handleRemoveHerb(index)">
|
||
删除
|
||
</el-button>
|
||
</div>
|
||
</el-form-item>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-section">
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="数量规格" prop="dose_count">
|
||
<div class="flex gap-2 items-center">
|
||
<el-input-number v-model="formData.dose_count" :min="1" :max="999" class="!w-28" />
|
||
<el-select v-model="formData.dose_unit" placeholder="单位" class="!w-24">
|
||
<el-option v-for="u in doseUnitOptions" :key="u" :label="u" :value="u" />
|
||
</el-select>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="用法" prop="usage_instruction">
|
||
<el-input v-model="formData.usage_instruction" placeholder="如:水煎服一日二次" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="医师" prop="doctor_name">
|
||
<el-input v-model="formData.doctor_name" placeholder="医师姓名" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="24">
|
||
<el-form-item label="医师签名" prop="doctor_signature" required>
|
||
<div class="signature-pad-wrap">
|
||
<canvas
|
||
ref="signatureCanvasRef"
|
||
class="signature-canvas"
|
||
width="280"
|
||
height="120"
|
||
@mousedown="onSignatureStart"
|
||
@mousemove="onSignatureMove"
|
||
@mouseup="onSignatureEnd"
|
||
@mouseleave="onSignatureEnd"
|
||
@touchstart.prevent="onSignatureStart"
|
||
@touchmove.prevent="onSignatureMove"
|
||
@touchend.prevent="onSignatureEnd"
|
||
/>
|
||
<div class="signature-actions">
|
||
<el-button size="small" @click="clearSignature">清空签名</el-button>
|
||
</div>
|
||
</div>
|
||
<div class="text-xs text-amber-600 mt-1">* 必填,请在框内手写医师签名</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</el-form>
|
||
|
||
</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>
|
||
<div class="prescription-type">
|
||
<el-tag v-if="savedPrescription.void_status === 1" type="danger" size="small">已作废</el-tag>
|
||
<template v-else>
|
||
<div>普通处方</div>
|
||
<div class="text-sm">当日有效</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
<div v-if="savedPrescription.void_status === 1" class="prescription-void-info">
|
||
作废人:{{ savedPrescription.void_by_name || '—' }},作废时间:{{ formatVoidTime(savedPrescription.void_time) }}
|
||
</div>
|
||
<div class="prescription-info">
|
||
<div class="info-row">
|
||
<span class="info-item"><em>日期</em>{{ savedPrescription.prescription_date }}</span>
|
||
<span class="info-item"><em>电话</em>{{ savedPrescription.phone }}</span>
|
||
<span class="info-item"><em>门诊号</em>{{ savedPrescription.visit_no }}</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-item"><em>姓名</em>{{ savedPrescription.patient_name }}</span>
|
||
<span class="info-item"><em>性别</em>{{ savedPrescription.gender_desc }}</span>
|
||
<span class="info-item"><em>年龄</em>{{ savedPrescription.age != null ? savedPrescription.age + '岁' : '—' }}</span>
|
||
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-item" v-if="savedPrescription.pulse"><em>面象</em>{{ savedPrescription.pulse || '—' }}</span>
|
||
<span class="info-item" v-if="savedPrescription.tongue"><em>舌象</em>{{ savedPrescription.tongue || '—' }}</span>
|
||
|
||
</div>
|
||
<div class="info-row info-row-full">
|
||
<span class="info-item"><em>临床诊断</em>{{ savedPrescription.clinical_diagnosis }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="prescription-rp">
|
||
<div class="rp-label">RP</div>
|
||
<div class="herbs-grid">
|
||
<div
|
||
v-for="(h, i) in savedPrescription.herbs"
|
||
:key="i"
|
||
class="herb-cell"
|
||
>
|
||
{{ h.name }} {{ h.dosage }}克
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="prescription-disclaimer" v-if="templateConfig.showDisclaimer">
|
||
{{ templateConfig.disclaimer }}
|
||
</div>
|
||
<div class="prescription-footer">
|
||
<div class="footer-left">
|
||
<div class="footer-dosage">
|
||
共{{ savedPrescription.dose_count }}{{ savedPrescription.dose_unit || '剂' }}, {{ savedPrescription.usage_instruction }}
|
||
</div>
|
||
<!-- <div class="footer-amount">金额 {{ savedPrescription.amount }}</div> -->
|
||
</div>
|
||
<div class="footer-right footer-sign">
|
||
<span class="signature-cell">
|
||
医师
|
||
<img
|
||
v-if="savedPrescription.doctor_signature"
|
||
:src="savedPrescription.doctor_signature"
|
||
alt="医师签名"
|
||
class="doctor-signature-img"
|
||
/>
|
||
<span v-else>{{ savedPrescription.doctor_name || '医师' }}</span>
|
||
</span>
|
||
<!-- <span>核对发药</span> -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="prescription-area">
|
||
<div >成都双流甄养堂互联网医院有限公司 联系方式:4001667339</div>
|
||
<div >地址 :四川省成都市双流区黄甲街道黄龙大道二段280号</div>
|
||
</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>
|
||
下载图片
|
||
</el-button>
|
||
<el-button type="primary" @click="handleDownloadPdf">
|
||
<el-icon><Document /></el-icon>
|
||
下载PDF
|
||
</el-button>
|
||
<el-button
|
||
v-if="savedPrescription.void_status !== 1"
|
||
type="danger"
|
||
plain
|
||
:loading="voiding"
|
||
@click="handleVoid"
|
||
>
|
||
作废处方
|
||
</el-button>
|
||
<el-button @click="handleNewPrescription">新建处方</el-button>
|
||
<el-button @click="handleClose">关闭</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<template v-if="!savedPrescription" #footer>
|
||
<div class="flex justify-end gap-2">
|
||
<el-button @click="handleClose">取消</el-button>
|
||
<el-button type="primary" @click="handleSave" :loading="saving">保存处方</el-button>
|
||
</div>
|
||
</template>
|
||
</el-drawer>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import feedback from '@/utils/feedback'
|
||
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 {
|
||
name: string
|
||
dosage: number
|
||
}
|
||
|
||
interface PrescriptionForm {
|
||
diagnosis_id: number
|
||
appointment_id: number
|
||
patient_name: string
|
||
gender: number
|
||
gender_desc: string
|
||
age: number
|
||
phone: string
|
||
visit_no: string
|
||
prescription_date: string
|
||
pulse: string
|
||
tongue: string
|
||
clinical_diagnosis: string
|
||
case_record: any
|
||
herbs: Herb[]
|
||
dose_count: number
|
||
dose_unit: string
|
||
usage_instruction: string
|
||
amount: number
|
||
doctor_name: string
|
||
doctor_signature: string
|
||
}
|
||
|
||
const emit = defineEmits(['success'])
|
||
|
||
const visible = ref(false)
|
||
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)
|
||
|
||
const templateConfig = reactive({
|
||
stationName: '成都双流甄养堂互联网医院',
|
||
showDisclaimer: true,
|
||
disclaimer: ''
|
||
})
|
||
|
||
const formData = reactive<PrescriptionForm>({
|
||
diagnosis_id: 0,
|
||
appointment_id: 0,
|
||
patient_name: '',
|
||
gender: 0,
|
||
gender_desc: '女',
|
||
age: 0,
|
||
phone: '',
|
||
visit_no: '',
|
||
prescription_date: '',
|
||
pulse: '',
|
||
tongue: '',
|
||
clinical_diagnosis: '',
|
||
case_record: null as any,
|
||
herbs: [],
|
||
dose_count: 1,
|
||
dose_unit: '剂',
|
||
usage_instruction: '水煎服一日二次',
|
||
amount: 0,
|
||
doctor_name: '',
|
||
doctor_signature: ''
|
||
})
|
||
|
||
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 ''
|
||
const item = options.find((opt: any) => opt.value === value)
|
||
return item ? item.name : value
|
||
}
|
||
|
||
const loadDictOptions = async () => {
|
||
try {
|
||
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: '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)
|
||
const stLabel = getDictLabel(syndromeTypeOptions.value, diagnosis?.syndrome_type)
|
||
const parts = [dtLabel, stLabel].filter(Boolean)
|
||
return parts.length ? parts.join(' ') : ''
|
||
}
|
||
|
||
const open = async (data: any, options?: { templateId?: number; stationName?: string }) => {
|
||
if (options?.stationName) templateConfig.stationName = options.stationName
|
||
|
||
const diagnosis = data.diagnosis || data
|
||
const diagnosisId = data.diagnosis_id ?? diagnosis?.id ?? data.id
|
||
if (!diagnosisId) {
|
||
feedback.msgWarning('缺少诊单信息')
|
||
return
|
||
}
|
||
|
||
await loadDictOptions()
|
||
|
||
const appointmentId = Number(data.id ?? data.appointment_id ?? 0)
|
||
if (appointmentId) {
|
||
try {
|
||
const existing = await prescriptionGetByAppointment({ appointment_id: appointmentId })
|
||
if (existing?.id) {
|
||
const detail = await prescriptionDetail({ id: existing.id })
|
||
savedPrescription.value = detail
|
||
visible.value = true
|
||
return
|
||
}
|
||
} 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 || ''
|
||
formData.gender = data.patient_gender ?? diagnosis.gender ?? 0
|
||
formData.gender_desc = formData.gender === 1 ? '男' : '女'
|
||
formData.age = data.patient_age ?? diagnosis.age ?? 0
|
||
formData.phone = data.patient_phone || diagnosis.phone || ''
|
||
formData.visit_no = data.id ? `1K${String(data.id).padStart(8, '0')}` : ''
|
||
formData.prescription_date = new Date().toISOString().slice(0, 10)
|
||
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 = '剂'
|
||
formData.usage_instruction = '水煎服一日二次'
|
||
formData.amount = 0
|
||
formData.doctor_name = userStore.userInfo?.name || ''
|
||
formData.doctor_signature = ''
|
||
|
||
savedPrescription.value = null
|
||
visible.value = true
|
||
setTimeout(() => initSignatureCanvas(), 100)
|
||
}
|
||
|
||
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('加载处方失败')
|
||
}
|
||
}
|
||
|
||
const handleClose = () => {
|
||
visible.value = false
|
||
savedPrescription.value = null
|
||
}
|
||
|
||
const getSignatureCoords = (e: MouseEvent | TouchEvent) => {
|
||
const canvas = signatureCanvasRef.value
|
||
if (!canvas) return { x: 0, y: 0 }
|
||
const rect = canvas.getBoundingClientRect()
|
||
const scaleX = canvas.width / rect.width
|
||
const scaleY = canvas.height / rect.height
|
||
if (e instanceof TouchEvent) {
|
||
const touch = e.touches[0] || e.changedTouches[0]
|
||
return {
|
||
x: (touch.clientX - rect.left) * scaleX,
|
||
y: (touch.clientY - rect.top) * scaleY
|
||
}
|
||
}
|
||
return {
|
||
x: (e.clientX - rect.left) * scaleX,
|
||
y: (e.clientY - rect.top) * scaleY
|
||
}
|
||
}
|
||
|
||
const onSignatureStart = (e: MouseEvent | TouchEvent) => {
|
||
isDrawing.value = true
|
||
const ctx = signatureCanvasRef.value?.getContext('2d')
|
||
if (!ctx) return
|
||
const { x, y } = getSignatureCoords(e)
|
||
ctx.beginPath()
|
||
ctx.moveTo(x, y)
|
||
}
|
||
|
||
const onSignatureMove = (e: MouseEvent | TouchEvent) => {
|
||
if (!isDrawing.value) return
|
||
const ctx = signatureCanvasRef.value?.getContext('2d')
|
||
if (!ctx) return
|
||
const { x, y } = getSignatureCoords(e)
|
||
ctx.lineTo(x, y)
|
||
ctx.stroke()
|
||
}
|
||
|
||
const onSignatureEnd = () => {
|
||
isDrawing.value = false
|
||
const canvas = signatureCanvasRef.value
|
||
if (canvas) {
|
||
formData.doctor_signature = canvas.toDataURL('image/png')
|
||
}
|
||
}
|
||
|
||
const clearSignature = () => {
|
||
const canvas = signatureCanvasRef.value
|
||
if (canvas) {
|
||
const ctx = canvas.getContext('2d')
|
||
if (ctx) {
|
||
ctx.fillStyle = '#fff'
|
||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||
}
|
||
formData.doctor_signature = ''
|
||
}
|
||
}
|
||
|
||
const initSignatureCanvas = () => {
|
||
const canvas = signatureCanvasRef.value
|
||
if (canvas) {
|
||
const ctx = canvas.getContext('2d')
|
||
if (ctx) {
|
||
ctx.fillStyle = '#fff'
|
||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||
ctx.strokeStyle = '#333'
|
||
ctx.lineWidth = 2
|
||
ctx.lineCap = 'round'
|
||
ctx.lineJoin = 'round'
|
||
}
|
||
}
|
||
}
|
||
|
||
const handleAddHerb = () => {
|
||
formData.herbs.push({ name: '', dosage: 6 })
|
||
}
|
||
|
||
const handleRemoveHerb = (index: number) => {
|
||
formData.herbs.splice(index, 1)
|
||
}
|
||
|
||
const handleSave = async () => {
|
||
if (!formData.clinical_diagnosis?.trim()) {
|
||
feedback.msgWarning('请输入临床诊断')
|
||
return
|
||
}
|
||
if (formData.herbs.length === 0) {
|
||
feedback.msgWarning('请至少添加一味中药')
|
||
return
|
||
}
|
||
for (const h of formData.herbs) {
|
||
if (!h.name?.trim()) {
|
||
feedback.msgWarning('请填写中药名称')
|
||
return
|
||
}
|
||
if (!h.dosage || h.dosage <= 0) {
|
||
feedback.msgWarning(`请填写「${h.name}」的剂量`)
|
||
return
|
||
}
|
||
}
|
||
|
||
if (!formData.doctor_signature?.trim()) {
|
||
feedback.msgWarning('请使用电子签名板手写医师签名')
|
||
return
|
||
}
|
||
|
||
saving.value = true
|
||
try {
|
||
const res = await prescriptionAdd({
|
||
diagnosis_id: formData.diagnosis_id,
|
||
appointment_id: formData.appointment_id,
|
||
patient_name: formData.patient_name,
|
||
gender: formData.gender,
|
||
age: formData.age,
|
||
phone: formData.phone,
|
||
visit_no: formData.visit_no,
|
||
prescription_date: formData.prescription_date,
|
||
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 || '剂',
|
||
usage_instruction: formData.usage_instruction,
|
||
amount: formData.amount,
|
||
doctor_name: formData.doctor_name,
|
||
doctor_signature: formData.doctor_signature
|
||
})
|
||
const id = res?.id
|
||
if (id) {
|
||
const detail = await prescriptionDetail({ id })
|
||
savedPrescription.value = detail
|
||
emit('success')
|
||
} else {
|
||
feedback.msgError('保存失败')
|
||
}
|
||
} catch (e: any) {
|
||
feedback.msgError(e?.msg || '保存失败')
|
||
} finally {
|
||
saving.value = false
|
||
}
|
||
}
|
||
|
||
const formatVoidTime = (ts: number | null | undefined) => {
|
||
if (!ts) return '—'
|
||
const d = new Date(ts * 1000)
|
||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`
|
||
}
|
||
|
||
const handleVoid = async () => {
|
||
const id = savedPrescription.value?.id
|
||
if (!id) return
|
||
try {
|
||
await feedback.confirm('确定要作废该处方吗?作废后不可恢复。')
|
||
voiding.value = true
|
||
await prescriptionVoid({ id })
|
||
feedback.msgSuccess('作废成功')
|
||
const detail = await prescriptionDetail({ id })
|
||
savedPrescription.value = detail
|
||
emit('success')
|
||
} catch (e: any) {
|
||
if (e !== 'cancel' && e?.message !== 'cancel') feedback.msgError(e?.msg || '作废失败')
|
||
} finally {
|
||
voiding.value = false
|
||
}
|
||
}
|
||
|
||
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) {
|
||
// 将患者信息、诊断信息填入表单,新建处方时保留
|
||
formData.diagnosis_id = saved.diagnosis_id ?? formData.diagnosis_id
|
||
formData.appointment_id = saved.appointment_id ?? formData.appointment_id
|
||
formData.patient_name = saved.patient_name || ''
|
||
formData.gender = saved.gender ?? 0
|
||
formData.gender_desc = saved.gender === 1 ? '男' : '女'
|
||
formData.age = saved.age ?? 0
|
||
formData.phone = saved.phone || ''
|
||
formData.visit_no = saved.visit_no || ''
|
||
formData.prescription_date = new Date().toISOString().slice(0, 10)
|
||
formData.pulse = saved.pulse || ''
|
||
formData.tongue = saved.tongue || ''
|
||
formData.clinical_diagnosis = saved.clinical_diagnosis || ''
|
||
formData.dose_count = 1
|
||
formData.dose_unit = saved.dose_unit || '剂'
|
||
formData.usage_instruction = '水煎服一日二次'
|
||
formData.amount = 0
|
||
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
|
||
}
|
||
|
||
const captureElement = async (): Promise<HTMLCanvasElement> => {
|
||
const el = prescriptionPrintRef.value
|
||
if (!el) throw new Error('未找到处方内容')
|
||
return html2canvas(el, {
|
||
scale: 2,
|
||
useCORS: true,
|
||
logging: false,
|
||
backgroundColor: '#ffffff'
|
||
})
|
||
}
|
||
|
||
/** 将元素内图片转为 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()
|
||
const link = document.createElement('a')
|
||
link.download = `处方_${savedPrescription.value?.patient_name || '未命名'}_${Date.now()}.png`
|
||
link.href = canvas.toDataURL('image/png')
|
||
link.click()
|
||
feedback.msgSuccess('图片已下载')
|
||
} catch (e) {
|
||
feedback.msgError('下载图片失败')
|
||
}
|
||
}
|
||
|
||
const handleDownloadPdf = async () => {
|
||
try {
|
||
const canvas = await captureElement()
|
||
const imgData = canvas.toDataURL('image/png')
|
||
const pdf = new jsPDF({
|
||
orientation: 'portrait',
|
||
unit: 'mm',
|
||
format: 'a5'
|
||
})
|
||
const pageW = pdf.internal.pageSize.getWidth()
|
||
const pageH = pdf.internal.pageSize.getHeight()
|
||
const scale = Math.min(pageW / canvas.width, pageH / canvas.height)
|
||
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) {
|
||
feedback.msgError('下载PDF失败')
|
||
}
|
||
}
|
||
|
||
defineExpose({
|
||
open,
|
||
openById
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.prescription-form {
|
||
.info-section {
|
||
margin-bottom: 20px;
|
||
padding: 16px;
|
||
background: #f5f7fa;
|
||
border-radius: 6px;
|
||
|
||
.section-title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
margin-bottom: 12px;
|
||
color: #303133;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.herb-item {
|
||
margin-bottom: 8px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.signature-pad-wrap {
|
||
border: 1px solid #dcdfe6;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
background: #fff;
|
||
|
||
.signature-canvas {
|
||
display: block;
|
||
cursor: crosshair;
|
||
touch-action: none;
|
||
}
|
||
|
||
.signature-actions {
|
||
padding: 8px;
|
||
border-top: 1px solid #ebeef5;
|
||
}
|
||
}
|
||
|
||
.prescription-print {
|
||
width: 148mm;
|
||
min-height: 210mm;
|
||
padding: 20px;
|
||
background: #fff;
|
||
font-size: 14px;
|
||
color: #333;
|
||
margin: 0 auto;
|
||
box-sizing: border-box;
|
||
|
||
.prescription-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
position: relative;
|
||
margin-bottom: 12px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px solid #ddd;
|
||
|
||
.prescription-title {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
margin: 0;
|
||
}
|
||
|
||
.prescription-type {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0;
|
||
}
|
||
}
|
||
|
||
.prescription-void-info {
|
||
font-size: 13px;
|
||
color: var(--el-color-danger);
|
||
margin-bottom: 12px;
|
||
padding: 8px 12px;
|
||
background: #fef0f0;
|
||
border-radius: 4px;
|
||
|
||
.prescription-title {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
margin: 0;
|
||
}
|
||
|
||
.prescription-type {
|
||
text-align: right;
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
|
||
.prescription-info {
|
||
margin-bottom: 16px;
|
||
padding: 12px 0;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
|
||
.info-row {
|
||
display: flex;
|
||
margin-bottom: 10px;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.info-item {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
}
|
||
|
||
.info-row-full {
|
||
.info-item {
|
||
flex: 1;
|
||
min-width: 100%;
|
||
}
|
||
}
|
||
|
||
.info-item {
|
||
display: inline-flex;
|
||
align-items: baseline;
|
||
gap: 6px;
|
||
font-size: 14px;
|
||
|
||
em {
|
||
font-style: normal;
|
||
color: #909399;
|
||
min-width: 56px;
|
||
}
|
||
}
|
||
|
||
.prescription-rp {
|
||
margin: 16px 0;
|
||
padding: 12px;
|
||
border: 1px solid #eee;
|
||
|
||
.rp-label {
|
||
font-weight: 600;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.herbs-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px 16px;
|
||
}
|
||
|
||
.herb-cell {
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.prescription-disclaimer {
|
||
text-align: center;
|
||
color: #e6a23c;
|
||
font-size: 12px;
|
||
margin: 12px 0;
|
||
height: 300px;
|
||
}
|
||
|
||
.prescription-area {
|
||
border-top: 1px dashed #ddd;
|
||
padding-top: 12px;
|
||
margin-top: 10px;
|
||
}
|
||
.prescription-footer {
|
||
margin-top: 16px;
|
||
padding-top: 12px;
|
||
border-top: 1px dashed #ddd;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
gap: 24px;
|
||
|
||
.footer-left {
|
||
.footer-dosage,
|
||
.footer-amount {
|
||
margin-bottom: 4px;
|
||
}
|
||
}
|
||
|
||
.footer-right.footer-sign {
|
||
text-align: right;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 24px;
|
||
}
|
||
|
||
.signature-cell {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.doctor-signature-img {
|
||
height: 28px;
|
||
max-width: 100px;
|
||
object-fit: contain;
|
||
vertical-align: middle;
|
||
}
|
||
}
|
||
}
|
||
|
||
.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>
|