import { getDictLabel, DICT_TYPE } from '@/utils/dict'; import {formatDate} from "@/utils/formatTime"; import { formatTime } from '@/utils' import html2pdf from 'html2pdf.js' import JSZip from 'jszip' import { saveAs } from 'file-saver' //导出床头卡 export const importCard = async (healthRecord,dataElder,type=1) => { // try { // 动态加载BedCardComponent let component = undefined if(healthRecord){ const { default: BedCardComponent } = await import('@/components/BedCardComponent/index.vue'); component = BedCardComponent; }else { const { default: ShareCardComponent } = await import('@/components/ShareCardComponent/index.vue'); component = ShareCardComponent; } const patientData = ref({}) // 使用封装的方法获取床头卡数据 patientData.value = getPatientData(healthRecord||{},dataElder); //console.log("药物过敏:",patientData.value) // 创建临时容器 const tempContainer = document.createElement('div'); tempContainer.style.position = 'absolute'; tempContainer.style.left = '-9999px'; tempContainer.style.top = '-9999px'; document.body.appendChild(tempContainer); // 创建Vue应用实例挂载组件 const { createApp } = await import('vue'); const app = createApp(component, { patientData: dataForm.value }); // 挂载组件 const instance = app.mount(tempContainer); // 等待组件渲染完成 await nextTick(); // 调用组件暴露的方法 if (instance && typeof instance.updatePatientData === 'function') { instance.updatePatientData(patientData.value); // 等待数据更新和DOM渲染完成 await nextTick(); await new Promise(resolve => setTimeout(resolve, 150)); } // 调用导出方法 if (instance && typeof instance.exportAsImage === 'function') { await instance.exportAsImage(type); } else { ElMessage.error('导出方法不可用'); } // 清理资源 app.unmount(); document.body.removeChild(tempContainer); // } catch (error) { // console.error('导出床头卡失败:', error); // ElMessage.error('导出失败,请重试'); // } } const dataForm = ref({}) // 封装床头卡数据的方法 export const getPatientData = (data,dataElder) => { try { dataForm.value = dataElder // 获取床位信息 const beStr = ['',''] try { const bdList = dataForm.value.bedName?.toString().split('-') beStr[0] = bdList[bdList?.length-2] beStr[1] = bdList[bdList?.length-1] } catch (e) {} // 解析Flag字段为中文 const parsedFlagData = parseFlagFields(data) console.log('解析后的Flag字段:', parsedFlagData) // 拼接病史信息 const medicalHistoryItems = [] // 常见疾病信息 const diseases = [ parsedFlagData.hypertensionText, parsedFlagData.diabetesText, parsedFlagData.coronaryDiseaseText, parsedFlagData.apoplexyText, parsedFlagData.tuberculosisText, parsedFlagData.copdText, parsedFlagData.malignantTumorsText, parsedFlagData.mentalIllnessText, parsedFlagData.hepatitisText, parsedFlagData.otherInfectiousDiseasesText, parsedFlagData.otherDiseasesText ] // 添加非空、非默认值的疾病 diseases.forEach(disease => { if (disease && disease !== '--' && disease !== '无') { medicalHistoryItems.push(disease) } }) // 获取饮食类型 const diet = { normal: '', soft: '', semiLiquid: '', liquid: '', nasogastric: '' } // 根据healthData.dietetic设置饮食类型 switch (parsedFlagData.dietetic) { case '1': diet.normal = '正常饮食' break case '2': diet.soft = '软食' break case '3': diet.semiLiquid = '半流质' break case '4': diet.liquid = '流质' break case '5': diet.nasogastric = '鼻饲' break } return { roomName: beStr[0], bedName: beStr[1], elderName: dataForm.value.elderName, elderSex: dataForm.value.elderSex?.toString()=='1'?'男':'女', birthday: formatDate(dataForm.value.birthday, 'YYYY-MM-DD'), checkInTime: dataForm.value.checkInTime ? formatTime(dataForm.value.checkInTime, 'yyyy-MM-dd') : '--', careLevel: dataForm.value.nurseLevelName, carePoints: parsedFlagData.specialCareNotes || '', drugAllergyHistoryText: parsedFlagData.drugAllergyHistoryText || '--', historyFoodAllergiesText: parsedFlagData.historyFoodAllergiesText || '--', medicalHistory: medicalHistoryItems.join('、') || '--', medicationHistory: parsedFlagData.pharmacy || '--', bloodTypeText: parsedFlagData.bloodTypeText || '--', familyDiseasesText: parsedFlagData.familyDiseasesText || '--', diet: diet, responsibleStaff: { nurseAide: '', nurse: '', doctor: '' }, qr:{ eId: dataForm.value.id, tId: dataForm.value.tenantId || parsedFlagData.tenantId }, // 添加解析后的Flag字段中文文本,方便床头卡组件使用 flagTexts: parsedFlagData } } catch (error) { console.error('处理床头卡数据时发生错误:', error) // 返回默认数据 return { roomName: '--', bedName: '--', elderName: dataForm.value.elderName || '--', elderSex: dataForm.value.elderSex?.toString()=='1'?'男':'女' || '--', birthday: formatDate(dataForm.value.birthday, 'YYYY-MM-DD') || '--', checkInTime: dataForm.value.checkInTime ? formatTime(dataForm.value.checkInTime, 'yyyy-MM-dd') : '--', careLevel: dataForm.value.nurseLevelName || '--', carePoints: '', allergies: '--', medicalHistory: '--', medicationHistory: '--', bloodTypeText: '--', diet: { normal: '', soft: '', semiLiquid: '', liquid: '', nasogastric: '' }, responsibleStaff: { nurseAide: '', nurse: '', doctor: '' }, qr:{ eId: dataForm.value.id || '', tId: dataForm.value.tenantId || '' }, flagTexts: {} } } } // 将数组字段和说明字段解析为中文的方法 const parseFlagFields = (data) => { // 解析后的中文结果 const parsedData = {} // 保留原始数据 Object.assign(parsedData, data || {}) // 解析数组字段和说明字段 try { // 解析药物过敏史数组 (对应Form.vue中的DICT_TYPE.DRUG_ALLERGY_ARR) if (data?.drugAllergyHistory) { let drugAllergies = [] if (Array.isArray(data.drugAllergyHistory)) { drugAllergies = data.drugAllergyHistory.map(item => { const label = getDictLabel(DICT_TYPE.DRUG_ALLERGY_ARR, item) // 如果获取不到标签,尝试清理数据 if (!label) { // 移除可能的前缀 return typeof item === 'string' ? item.replace(/^\d+-/, '') : item } return label }) // drugAllergies = data.drugAllergyHistory.map(item => getDictLabel(DICT_TYPE.DRUG_ALLERGY_ARR, item) || item) } else if (typeof data.drugAllergyHistory === 'string' && data.drugAllergyHistory !== '[]') { // 处理可能的JSON字符串 try { const allergies = JSON.parse(data.drugAllergyHistory) if (Array.isArray(allergies)) { drugAllergies = allergies.map(item => getDictLabel(DICT_TYPE.DRUG_ALLERGY_ARR, item) || item) } } catch (e) { // 如果不是JSON字符串,保持原样 drugAllergies = [data.drugAllergyHistory] } } parsedData.drugAllergyHistoryText = drugAllergies.join('、') || '--' if (parsedData.drugAllergyHistoryText && typeof parsedData.drugAllergyHistoryText === 'string') { parsedData.drugAllergyHistoryText = parsedData.drugAllergyHistoryText.replace('0-', '') } } // 解析食物过敏史数组 (对应Form.vue中的DICT_TYPE.FOOD_ALLERGY_ARR) if (data?.historyFoodAllergies) { let foodAllergies = [] if (Array.isArray(data.historyFoodAllergies)) { foodAllergies = data.historyFoodAllergies.map(item => getDictLabel(DICT_TYPE.FOOD_ALLERGY_ARR, item) || item) } else if (typeof data.historyFoodAllergies === 'string' && data.historyFoodAllergies !== '[]') { try { const allergies = JSON.parse(data.historyFoodAllergies) if (Array.isArray(allergies)) { foodAllergies = allergies.map(item => getDictLabel(DICT_TYPE.FOOD_ALLERGY_ARR, item) || item) } } catch (e) { foodAllergies = [data.historyFoodAllergies] } } parsedData.historyFoodAllergiesText = foodAllergies.join('、') || '--' if (parsedData.historyFoodAllergiesText && typeof parsedData.historyFoodAllergiesText === 'string') { parsedData.historyFoodAllergiesText = parsedData.historyFoodAllergiesText.replace('0-', '') } } // 解析毒物种类数组 (对应Form.vue中的DICT_TYPE.POISONS_ARR) if (data?.typesToxins) { let toxins = [] if (Array.isArray(data.typesToxins)) { toxins = data.typesToxins.map(item => getDictLabel(DICT_TYPE.POISONS_ARR, item) || item) } else if (typeof data.typesToxins === 'string' && data.typesToxins !== '[]') { try { const types = JSON.parse(data.typesToxins) if (Array.isArray(types)) { toxins = types.map(item => getDictLabel(DICT_TYPE.POISONS_ARR, item) || item) } } catch (e) { toxins = [data.typesToxins] } } parsedData.typesToxinsText = toxins.join('、') || '--' } // 解析残疾情况数组 (对应Form.vue中的DICT_TYPE.DISABILITY_ARR) if (data?.disabilitySituation) { let disabilities = [] if (Array.isArray(data.disabilitySituation)) { disabilities = data.disabilitySituation.map(item => getDictLabel(DICT_TYPE.DISABILITY_ARR, item) || item) } parsedData.disabilitySituationText = disabilities.join('、') || '--' } // 解析既往疾病数组 (对应Form.vue中的DICT_TYPE.SICK_ARR) if (data?.preexistingDisease) { let diseases = [] if (Array.isArray(data.preexistingDisease)) { diseases = data.preexistingDisease.map(item => getDictLabel(DICT_TYPE.SICK_ARR, item) || item) } parsedData.preexistingDiseaseText = diseases.join('、') || '--' } // 解析家族疾病数组 (对应Form.vue中的DICT_TYPE.SICK_ARR) if (data?.familyDiseases) { let familyDiseases = [] if (Array.isArray(data.familyDiseases)) { familyDiseases = data.familyDiseases.map(item => getDictLabel(DICT_TYPE.SICK_ARR, item) || item) } parsedData.familyDiseasesText = familyDiseases.join('、') || '--' } // 解析血型 parsedData.bloodTypeText = data.bloodType?(data.bloodType+'型'):'--' // 解析疾病说明字段 parsedData.hypertensionText = data.hypertension || (data.hypertensionFlag === '1' ? '高血压' : '--') parsedData.diabetesText = data.diabetes || (data.diabetesFlag === '1' ? '糖尿病' : '--') parsedData.coronaryDiseaseText = data.coronaryDisease || (data.coronaryDiseaseFlag === '1' ? '冠心病' : '--') parsedData.apoplexyText = data.apoplexy || (data.apoplexyFlag === '1' ? '脑卒中' : '--') parsedData.tuberculosisText = data.tuberculosis || (data.tuberculosisFlag === '1' ? '结核病' : '--') parsedData.copdText = data.copd || (data.copdFlag === '1' ? '慢性阻塞性肺疾病' : '--') parsedData.malignantTumorsText = data.malignantTumors || (data.malignantTumorsFlag === '1' ? '恶性肿瘤' : '--') parsedData.mentalIllnessText = data.mentalIllness || (data.mentalIllnessFlag === '1' ? '重性精神病' : '--') parsedData.hepatitisText = data.hepatitis || (data.hepatitisFlag === '1' ? '肝炎' : '--') parsedData.otherInfectiousDiseasesText = data.otherInfectiousDiseases || (data.otherInfectiousDiseasesFlag === '1' ? '其他法定传染病' : '--') parsedData.otherDiseasesText = data.otherDiseases || (data.otherDiseasesFlag === '1' ? '其他疾病' : '--') } catch (error) { console.error('解析字段时发生错误:', error) } return parsedData } interface PDFOptions { margin?: number[] filename?: string image?: { type?: 'jpeg' | 'png' quality?: number } html2canvas?: { scale?: number useCORS?: boolean logging?: boolean letterRendering?: boolean allowTaint?: boolean backgroundColor?: string foreignObjectRendering?: boolean imageTimeout?: number removeContainer?: boolean } jsPDF?: { unit?: string format?: string orientation?: string compress?: boolean } } interface PDFItem { element: HTMLElement filename: string options?: PDFOptions } const defaultPDFOptions: PDFOptions = { margin: [10, 10, 10, 10], filename: 'document.pdf', image: { type: 'jpeg', quality: 1 }, html2canvas: { scale: 0.8, // useCORS: true, // logging: false, // letterRendering: true, // allowTaint: true, // backgroundColor: '#ffffff', // foreignObjectRendering: false, // imageTimeout: 5000, // removeContainer: true }, jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait', compress: true } } const fixInputStyles = (element: HTMLElement) => { const clone = element.cloneNode(true) as HTMLElement const inputs = clone.querySelectorAll('input, textarea, select') inputs.forEach(input => { const tagName = input.tagName.toLowerCase() const parent = input.parentElement if (!parent) return if (tagName === 'textarea') { const textarea = input as HTMLTextAreaElement const div = document.createElement('div') div.className = '' div.style.border = '1px solid #dcdfe6' div.style.backgroundColor = '#ffffff' div.style.padding = '8px' div.style.borderRadius = '4px' div.style.color = '#606266' div.style.minHeight = textarea.style.minHeight || '80px' div.style.whiteSpace = 'pre-wrap' div.style.wordWrap = 'break-word' div.style.overflow = 'visible' div.style.boxSizing = 'border-box' div.style.display = 'block' if (textarea.style.width && textarea.style.width !== 'auto') { div.style.width = textarea.style.width } div.textContent = textarea.value || textarea.textContent || '' parent.replaceChild(div, textarea) } else if (tagName === 'input') { const inputEl = input as HTMLInputElement const inputType = inputEl.type if (inputType === 'checkbox' || inputType === 'radio') { inputEl.style.opacity = '1' inputEl.style.display = 'inline-block' inputEl.style.backgroundColor = 'transparent' } else { const span = document.createElement('span') span.className = '' span.style.border = '1px solid #dcdfe6' span.style.backgroundColor = '#ffffff' span.style.padding = '4px' span.style.borderRadius = '4px' span.style.color = '#606266' span.style.display = 'inline-block' span.style.minWidth = '82px' span.style.minHeight = '30px' span.style.boxSizing = 'border-box' if (inputEl.style.width && inputEl.style.width !== 'auto') { span.style.width = inputEl.style.width } span.style.lineHeight = '30px' span.style.verticalAlign = 'middle' span.style.overflow = 'hidden' span.style.textOverflow = 'ellipsis' span.style.whiteSpace = 'nowrap' span.textContent = inputEl.value || inputEl.placeholder || '' parent.replaceChild(span, inputEl) } } else if (tagName === 'select') { const selectEl = input as HTMLSelectElement const span = document.createElement('span') span.className = '' span.style.border = '1px solid #dcdfe6' span.style.backgroundColor = '#ffffff' span.style.padding = '8px' span.style.borderRadius = '4px' span.style.color = '#606266' span.style.display = 'inline-block' span.style.minWidth = '100px' span.style.minHeight = '32px' span.style.boxSizing = 'border-box' if (selectEl.style.width && selectEl.style.width !== 'auto') { span.style.width = selectEl.style.width } span.style.lineHeight = '32px' span.style.verticalAlign = 'middle' span.style.overflow = 'hidden' span.style.textOverflow = 'ellipsis' span.style.whiteSpace = 'nowrap' const selectedOption = selectEl.options[selectEl.selectedIndex] span.textContent = selectedOption ? selectedOption.text : '' parent.replaceChild(span, selectEl) } }) const allElements = clone.querySelectorAll('*') // allElements.forEach(el => { // const element = el as HTMLElement // element.style.setProperty('background-color', '#ffffff', 'important') // element.style.setProperty('background', '#ffffff', 'important') // element.style.setProperty('box-shadow', 'none', 'important') // }) const elInputs = clone.querySelectorAll('.el-input__inner, .el-textarea__inner') elInputs.forEach(input => { const element = input as HTMLElement const parent = element.parentElement if (!parent) return if (element.classList.contains('el-textarea__inner')) { const div = document.createElement('div') div.className = '' div.style.border = '1px solid #dcdfe6' div.style.backgroundColor = '#ffffff' div.style.padding = '8px' div.style.borderRadius = '4px' div.style.color = '#606266' div.style.minHeight = element.style.minHeight || '80px' div.style.whiteSpace = 'pre-wrap' div.style.wordWrap = 'break-word' div.style.overflow = 'visible' div.style.boxSizing = 'border-box' div.style.display = 'block' if (element.style.width && element.style.width !== 'auto') { div.style.width = element.style.width } div.textContent = element.textContent || '' parent.replaceChild(div, element) } else { element.style.border = '1px solid #dcdfe6' element.style.backgroundColor = '#ffffff' element.style.color = '#606266' element.style.boxSizing = 'border-box' } }) const elWrappers = clone.querySelectorAll('.el-input, .el-input__wrapper, .el-textarea') elWrappers.forEach(wrapper => { const element = wrapper as HTMLElement element.style.setProperty('background-color', 'transparent', 'important') element.style.setProperty('background', 'transparent', 'important') element.style.setProperty('border', 'none', 'important') element.style.setProperty('box-shadow', 'none', 'important') element.style.setProperty('padding', '0', 'important') element.style.setProperty('box-sizing', 'border-box', 'important') }) return clone } const generateSinglePDF = async (item: PDFItem): Promise<{ filename: string, blob: Blob }> => { const options = { ...defaultPDFOptions, ...item.options } //fixInputStyles(item.element) const pdfBlob = await new Promise((resolve, reject) => { html2pdf() .set(options) .from(item.element) .toPdf() .output('blob') .then(resolve) .catch(reject) }) return { filename: item.filename.endsWith('.pdf') ? item.filename : `${item.filename}.pdf`, blob: pdfBlob } } interface BatchPDFProcessOptions { dataList: T[] elementId: string processItem: (item: T, index: number) => Promise getFilename: (item: T, index: number) => string zipFileName: string pdfOptions?: PDFOptions onProgress?: (current: number, total: number) => void } export const batchGeneratePDFsAndZipWithProcess = async ( options: BatchPDFProcessOptions ): Promise => { const { dataList, elementId, processItem, getFilename, zipFileName, pdfOptions, onProgress } = options try { const zip = new JSZip() const pdfResults: { filename: string; blob: Blob }[] = [] for (let i = 0; i < dataList.length; i++) { const item = dataList[i] try { await processItem(item, i) await nextTick() const element = document.getElementById(elementId) if (!element) { console.warn(`找不到元素: ${elementId}`) continue } const clonedElement = fixInputStyles(element) const filename = getFilename(item, i) const mergedOptions = { ...defaultPDFOptions, ...pdfOptions } const pdfBlob = await new Promise((resolve, reject) => { html2pdf() .set(mergedOptions) .from(clonedElement) .toPdf() .output('blob') .then(resolve) .catch(reject) }) pdfResults.push({ filename: filename.endsWith('.pdf') ? filename : `${filename}.pdf`, blob: pdfBlob }) if (onProgress) { onProgress(i + 1, dataList.length) } } catch (error) { console.error(`处理第 ${i + 1} 项时出错:`, error) } } pdfResults.forEach(({ filename, blob }) => { zip.file(filename, blob) }) const zipBlob = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE', compressionOptions: { level: 6 } }) saveAs(zipBlob, zipFileName.endsWith('.zip') ? zipFileName : `${zipFileName}.zip`) console.log(`成功生成 ${pdfResults.length} 个 PDF 文件并打包为 ${zipFileName}`) } catch (error) { console.error('批量生成 PDF 并打包失败:', error) throw error } } export const generatePDFBlob = async ( element: HTMLElement, options?: PDFOptions ): Promise => { const mergedOptions = { ...defaultPDFOptions, ...options } //fixInputStyles(element) return new Promise((resolve, reject) => { html2pdf() .set(mergedOptions) .from(element) .toPdf() .output('blob') .then(resolve) .catch(reject) }) } export const downloadAsZip = async ( pdfBlob: Blob, fileName: string = 'document.pdf', zipFileName: string = 'document.zip' ): Promise => { try { const zip = new JSZip() const pdfFileName = fileName.endsWith('.pdf') ? fileName : `${fileName}.pdf` zip.file(pdfFileName, pdfBlob) const zipBlob = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE', compressionOptions: { level: 6 } }) const finalZipName = zipFileName.endsWith('.zip') ? zipFileName : `${zipFileName}.zip` saveAs(zipBlob, finalZipName) console.log('压缩文件下载成功') } catch (error) { console.error('压缩文件下载失败:', error) throw error } }