outBedCard.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. import { getDictLabel, DICT_TYPE } from '@/utils/dict';
  2. import {formatDate} from "@/utils/formatTime";
  3. import { formatTime } from '@/utils'
  4. let html2pdfInstance: any = null
  5. const getHtml2pdf = async () => {
  6. if (!html2pdfInstance) {
  7. html2pdfInstance = (await import('html2pdf.js')).default
  8. }
  9. return html2pdfInstance
  10. }
  11. let JSZipInstance: any = null
  12. const getJSZip = async () => {
  13. if (!JSZipInstance) {
  14. JSZipInstance = (await import('jszip')).default
  15. }
  16. return JSZipInstance
  17. }
  18. let saveAsFn: any = null
  19. const getSaveAs = async () => {
  20. if (!saveAsFn) {
  21. saveAsFn = (await import('file-saver')).saveAs
  22. }
  23. return saveAsFn
  24. }
  25. //导出床头卡
  26. export const importCard = async (healthRecord,dataElder,type=1) => {
  27. // try {
  28. // 动态加载BedCardComponent
  29. let component = undefined
  30. if(healthRecord){
  31. const { default: BedCardComponent } = await import('@/components/BedCardComponent/index.vue');
  32. component = BedCardComponent;
  33. }else {
  34. const { default: ShareCardComponent } = await import('@/components/ShareCardComponent/index.vue');
  35. component = ShareCardComponent;
  36. }
  37. const patientData = ref({})
  38. // 使用封装的方法获取床头卡数据
  39. patientData.value = getPatientData(healthRecord||{},dataElder);
  40. console.log("导出床头卡组装数据:",patientData.value)
  41. // 创建临时容器
  42. const tempContainer = document.createElement('div');
  43. tempContainer.style.position = 'absolute';
  44. tempContainer.style.left = '-9999px';
  45. tempContainer.style.top = '-9999px';
  46. document.body.appendChild(tempContainer);
  47. // 创建Vue应用实例挂载组件
  48. const { createApp } = await import('vue');
  49. const app = createApp(component, {
  50. patientData: patientData.value
  51. });
  52. // 挂载组件
  53. const instance = app.mount(tempContainer);
  54. // 等待组件渲染完成
  55. await nextTick();
  56. // 调用组件暴露的方法
  57. if (instance && typeof instance.updatePatientData === 'function') {
  58. instance.updatePatientData(patientData.value);
  59. // 等待数据更新和DOM渲染完成
  60. await nextTick();
  61. await new Promise(resolve => setTimeout(resolve, 150));
  62. }
  63. // 调用导出方法
  64. if (instance && typeof instance.exportAsImage === 'function') {
  65. await instance.exportAsImage(type);
  66. } else {
  67. ElMessage.error('导出方法不可用');
  68. }
  69. // 清理资源
  70. app.unmount();
  71. document.body.removeChild(tempContainer);
  72. // } catch (error) {
  73. // console.error('导出床头卡失败:', error);
  74. // ElMessage.error('导出失败,请重试');
  75. // }
  76. }
  77. const dataForm = ref({})
  78. // 封装床头卡数据的方法
  79. export const getPatientData = (data,dataElder) => {
  80. try {
  81. console.log('床头卡原始健康档案数据:', data)
  82. console.log('床头卡原始长者详情数据:', dataElder)
  83. dataForm.value = dataElder
  84. // 获取床位信息
  85. // const beStr = ['', '']
  86. // try {
  87. // const bdList = dataForm.value.bedName?.toString().split('-')
  88. // beStr[1] = bdList[bdList?.length - 1]?.replace(/床$/, '')
  89. // beStr[0] = bdList[bdList?.length - 2] +' - ' + beStr[1]
  90. //
  91. // } catch (e) {}
  92. // 解析Flag字段为中文
  93. const parsedFlagData = parseFlagFields(data)
  94. console.log('解析后的Flag字段:', parsedFlagData)
  95. // 拼接病史信息
  96. const medicalHistoryItems = []
  97. // 常见疾病信息
  98. const diseases = [
  99. parsedFlagData.hypertensionText,
  100. parsedFlagData.diabetesText,
  101. parsedFlagData.coronaryDiseaseText,
  102. parsedFlagData.apoplexyText,
  103. parsedFlagData.tuberculosisText,
  104. parsedFlagData.copdText,
  105. parsedFlagData.malignantTumorsText,
  106. parsedFlagData.mentalIllnessText,
  107. parsedFlagData.hepatitisText,
  108. parsedFlagData.otherInfectiousDiseasesText,
  109. parsedFlagData.otherDiseasesText
  110. ]
  111. // 添加非空、非默认值的疾病
  112. diseases.forEach(disease => {
  113. if (disease && disease !== '--' && disease !== '无') {
  114. medicalHistoryItems.push(disease)
  115. }
  116. })
  117. // 获取饮食类型
  118. const diet = {
  119. normal: '',
  120. soft: '',
  121. semiLiquid: '',
  122. liquid: '',
  123. nasogastric: ''
  124. }
  125. // 根据healthData.dietetic设置饮食类型
  126. switch (String(parsedFlagData.dietetic)) {
  127. case '1':
  128. diet.normal = '正常饮食'
  129. break
  130. case '2':
  131. diet.soft = '软食'
  132. break
  133. case '3':
  134. diet.semiLiquid = '半流质'
  135. break
  136. case '4':
  137. diet.liquid = '流质'
  138. break
  139. case '5':
  140. diet.nasogastric = '鼻饲'
  141. break
  142. }
  143. // 合并过敏史
  144. const allergies: string[] = []
  145. if (parsedFlagData.drugAllergyHistoryText && parsedFlagData.drugAllergyHistoryText !== '--') {
  146. allergies.push(parsedFlagData.drugAllergyHistoryText)
  147. }
  148. if (
  149. parsedFlagData.historyFoodAllergiesText &&
  150. parsedFlagData.historyFoodAllergiesText !== '--'
  151. ) {
  152. allergies.push(parsedFlagData.historyFoodAllergiesText)
  153. }
  154. // 组装最终数据
  155. const result = {
  156. roomName: '',
  157. bedName: dataForm.value.bedName,
  158. elderName: dataForm.value.elderName,
  159. elderSex: dataForm.value.elderSex?.toString() == '1' ? '男' : '女',
  160. birthday: formatDate(dataForm.value.birthday, 'YYYY-MM-DD'),
  161. checkInTime: dataForm.value.checkInTime ? formatTime(dataForm.value.checkInTime, 'yyyy-MM-dd') : '--',
  162. careLevel: dataForm.value.nurseLevelName,
  163. carePoints: data?.careNote || '',
  164. drugAllergyHistoryText: parsedFlagData.drugAllergyHistoryText || '--',
  165. historyFoodAllergiesText: parsedFlagData.historyFoodAllergiesText || '--',
  166. allergiesText: allergies.join('、') || '--',
  167. medicalHistory: parsedFlagData.preexistingDiseaseText || '--',
  168. medicationHistory: data?.medicationHistory || '--',
  169. bloodTypeText: parsedFlagData.bloodTypeText || '--',
  170. familyDiseasesText: parsedFlagData.familyDiseasesText || '--',
  171. diet: diet,
  172. dietaryGuidelines: data?.dietaryGuidelines || '',
  173. responsibleStaff: {
  174. nurseAide: data?.nurseAssistant || '',
  175. nurse: data?.nurse || '',
  176. doctor: data?.doctor || ''
  177. },
  178. asphyxiationByChoking: data?.asphyxiationByChoking || '',
  179. pressureSores: data?.pressureSores || '',
  180. fallPreventionMeasures: data?.fallPreventionMeasures || '',
  181. assessSimpleMentalState: data?.assessSimpleMentalState || '',
  182. empyrosis: data?.empyrosis || '',
  183. fallDown: data?.fallDown || '',
  184. wanderAway: data?.wanderAway || '',
  185. antiEntertainment: data?.antiEntertainment || '',
  186. commitSuicide: data?.commitSuicide || '',
  187. qr: {
  188. eId: dataForm.value.id,
  189. tId: dataForm.value.tenantId || parsedFlagData.tenantId
  190. },
  191. // 添加解析后的Flag字段中文文本,方便床头卡组件使用
  192. flagTexts: parsedFlagData
  193. }
  194. console.log('床头卡组装后数据:', result)
  195. return result
  196. } catch (error) {
  197. console.error('处理床头卡数据时发生错误:', error)
  198. // 返回默认数据
  199. return {
  200. roomName: '--',
  201. bedName: '--',
  202. elderName: dataForm.value.elderName || '--',
  203. elderSex: dataForm.value.elderSex?.toString()=='1'?'男':'女' || '--',
  204. birthday: formatDate(dataForm.value.birthday, 'YYYY-MM-DD') || '--',
  205. checkInTime: dataForm.value.checkInTime ? formatTime(dataForm.value.checkInTime, 'yyyy-MM-DD') : '--',
  206. careLevel: dataForm.value.nurseLevelName || '--',
  207. carePoints: '',
  208. allergiesText: '--',
  209. medicalHistory: '--',
  210. medicationHistory: '--',
  211. bloodTypeText: '--',
  212. dietaryGuidelines: '',
  213. diet: {
  214. normal: '',
  215. soft: '',
  216. semiLiquid: '',
  217. liquid: '',
  218. nasogastric: ''
  219. },
  220. responsibleStaff: {
  221. nurseAide: '',
  222. nurse: '',
  223. doctor: ''
  224. },
  225. qr:{
  226. eId: dataForm.value.id || '',
  227. tId: dataForm.value.tenantId || ''
  228. },
  229. flagTexts: {}
  230. }
  231. }
  232. }
  233. // 将数组字段和说明字段解析为中文的方法
  234. const parseFlagFields = (data) => {
  235. // 解析后的中文结果
  236. const parsedData = {}
  237. // 保留原始数据
  238. Object.assign(parsedData, data || {})
  239. // 如果数据为空,直接返回默认值
  240. if (!data) {
  241. parsedData.drugAllergyHistoryText = '--'
  242. parsedData.historyFoodAllergiesText = '--'
  243. parsedData.typesToxinsText = '--'
  244. parsedData.disabilitySituationText = '--'
  245. parsedData.preexistingDiseaseText = '--'
  246. parsedData.familyDiseasesText = '--'
  247. parsedData.bloodTypeText = '--'
  248. return parsedData
  249. }
  250. // 解析数组字段和说明字段
  251. try {
  252. // 解析药物过敏史数组 (对应Form.vue中的DICT_TYPE.DRUG_ALLERGY_ARR)
  253. if (data?.drugAllergyHistory) {
  254. let drugAllergies = []
  255. if (Array.isArray(data.drugAllergyHistory)) {
  256. drugAllergies = data.drugAllergyHistory.map(item => {
  257. const label = getDictLabel(DICT_TYPE.DRUG_ALLERGY_ARR, item)
  258. // 如果获取不到标签,尝试清理数据
  259. if (!label) {
  260. // 移除可能的前缀
  261. return typeof item === 'string' ? item.replace(/^\d+-/, '') : item
  262. }
  263. return label
  264. })
  265. // drugAllergies = data.drugAllergyHistory.map(item => getDictLabel(DICT_TYPE.DRUG_ALLERGY_ARR, item) || item)
  266. } else if (typeof data.drugAllergyHistory === 'string' && data.drugAllergyHistory !== '[]') {
  267. // 处理可能的JSON字符串
  268. try {
  269. const allergies = JSON.parse(data.drugAllergyHistory)
  270. if (Array.isArray(allergies)) {
  271. drugAllergies = allergies.map(item => getDictLabel(DICT_TYPE.DRUG_ALLERGY_ARR, item) || item)
  272. }
  273. } catch (e) {
  274. // 如果不是JSON字符串,保持原样
  275. drugAllergies = [data.drugAllergyHistory]
  276. }
  277. }
  278. parsedData.drugAllergyHistoryText = drugAllergies.join('、') || '--'
  279. if (parsedData.drugAllergyHistoryText && typeof parsedData.drugAllergyHistoryText === 'string') {
  280. parsedData.drugAllergyHistoryText = parsedData.drugAllergyHistoryText.replace('0-', '')
  281. }
  282. }
  283. // 解析食物过敏史数组 (对应Form.vue中的DICT_TYPE.FOOD_ALLERGY_ARR)
  284. if (data?.historyFoodAllergies) {
  285. let foodAllergies = []
  286. if (Array.isArray(data.historyFoodAllergies)) {
  287. foodAllergies = data.historyFoodAllergies.map(item => getDictLabel(DICT_TYPE.FOOD_ALLERGY_ARR, item) || item)
  288. } else if (typeof data.historyFoodAllergies === 'string' && data.historyFoodAllergies !== '[]') {
  289. try {
  290. const allergies = JSON.parse(data.historyFoodAllergies)
  291. if (Array.isArray(allergies)) {
  292. foodAllergies = allergies.map(item => getDictLabel(DICT_TYPE.FOOD_ALLERGY_ARR, item) || item)
  293. }
  294. } catch (e) {
  295. foodAllergies = [data.historyFoodAllergies]
  296. }
  297. }
  298. parsedData.historyFoodAllergiesText = foodAllergies.join('、') || '--'
  299. if (parsedData.historyFoodAllergiesText && typeof parsedData.historyFoodAllergiesText === 'string') {
  300. parsedData.historyFoodAllergiesText = parsedData.historyFoodAllergiesText.replace('0-', '')
  301. }
  302. }
  303. // 解析毒物种类数组 (对应Form.vue中的DICT_TYPE.POISONS_ARR)
  304. if (data?.typesToxins) {
  305. let toxins = []
  306. if (Array.isArray(data.typesToxins)) {
  307. toxins = data.typesToxins.map(item => getDictLabel(DICT_TYPE.POISONS_ARR, item) || item)
  308. } else if (typeof data.typesToxins === 'string' && data.typesToxins !== '[]') {
  309. try {
  310. const types = JSON.parse(data.typesToxins)
  311. if (Array.isArray(types)) {
  312. toxins = types.map(item => getDictLabel(DICT_TYPE.POISONS_ARR, item) || item)
  313. }
  314. } catch (e) {
  315. toxins = [data.typesToxins]
  316. }
  317. }
  318. parsedData.typesToxinsText = toxins.join('、') || '--'
  319. }
  320. // 解析残疾情况数组 (对应Form.vue中的DICT_TYPE.DISABILITY_ARR)
  321. if (data?.disabilitySituation) {
  322. let disabilities = []
  323. if (Array.isArray(data.disabilitySituation)) {
  324. disabilities = data.disabilitySituation.map(item => getDictLabel(DICT_TYPE.DISABILITY_ARR, item) || item)
  325. }
  326. parsedData.disabilitySituationText = disabilities.join('、') || '--'
  327. }
  328. // 解析既往疾病数组 (对应Form.vue中的DICT_TYPE.SICK_ARR)
  329. if (data?.preexistingDisease) {
  330. let diseases = []
  331. const val = data.preexistingDisease
  332. if (Array.isArray(val)) {
  333. diseases = val.map(item => getDictLabel(DICT_TYPE.SICK_ARR, item) || item)
  334. } else if (typeof val === 'string') {
  335. const trimmed = val.trim()
  336. if (trimmed && trimmed !== '[]') {
  337. if (trimmed.startsWith('[')) {
  338. try {
  339. const diseaseList = JSON.parse(trimmed)
  340. if (Array.isArray(diseaseList)) {
  341. diseases = diseaseList.map(item => getDictLabel(DICT_TYPE.SICK_ARR, item) || item)
  342. }
  343. } catch (e) {
  344. diseases = trimmed.split(/[,,]/).map(s => s.trim()).filter(Boolean)
  345. }
  346. } else {
  347. diseases = trimmed.split(/[,,]/).map(s => s.trim()).filter(Boolean)
  348. }
  349. }
  350. }
  351. parsedData.preexistingDiseaseText = diseases.join('、') || '--'
  352. }
  353. // 解析家族疾病数组 (对应Form.vue中的DICT_TYPE.SICK_ARR)
  354. if (data?.familyDiseases) {
  355. let familyDiseases = []
  356. if (Array.isArray(data.familyDiseases)) {
  357. familyDiseases = data.familyDiseases.map(item => getDictLabel(DICT_TYPE.SICK_ARR, item) || item)
  358. }
  359. parsedData.familyDiseasesText = familyDiseases.join('、') || '--'
  360. }
  361. // 解析血型
  362. parsedData.bloodTypeText = data.bloodType?(data.bloodType+'型'):'--'
  363. // 解析疾病说明字段
  364. parsedData.hypertensionText = data.hypertension || (data.hypertensionFlag === '1' ? '高血压' : '--')
  365. parsedData.diabetesText = data.diabetes || (data.diabetesFlag === '1' ? '糖尿病' : '--')
  366. parsedData.coronaryDiseaseText = data.coronaryDisease || (data.coronaryDiseaseFlag === '1' ? '冠心病' : '--')
  367. parsedData.apoplexyText = data.apoplexy || (data.apoplexyFlag === '1' ? '脑卒中' : '--')
  368. parsedData.tuberculosisText = data.tuberculosis || (data.tuberculosisFlag === '1' ? '结核病' : '--')
  369. parsedData.copdText = data.copd || (data.copdFlag === '1' ? '慢性阻塞性肺疾病' : '--')
  370. parsedData.malignantTumorsText = data.malignantTumors || (data.malignantTumorsFlag === '1' ? '恶性肿瘤' : '--')
  371. parsedData.mentalIllnessText = data.mentalIllness || (data.mentalIllnessFlag === '1' ? '重性精神病' : '--')
  372. parsedData.hepatitisText = data.hepatitis || (data.hepatitisFlag === '1' ? '肝炎' : '--')
  373. parsedData.otherInfectiousDiseasesText = data.otherInfectiousDiseases || (data.otherInfectiousDiseasesFlag === '1' ? '其他法定传染病' : '--')
  374. parsedData.otherDiseasesText = data.otherDiseases || (data.otherDiseasesFlag === '1' ? '其他疾病' : '--')
  375. } catch (error) {
  376. console.error('解析字段时发生错误:', error)
  377. }
  378. return parsedData
  379. }
  380. interface PDFOptions {
  381. margin?: number[]
  382. filename?: string
  383. image?: {
  384. type?: 'jpeg' | 'png'
  385. quality?: number
  386. }
  387. html2canvas?: {
  388. scale?: number
  389. useCORS?: boolean
  390. logging?: boolean
  391. letterRendering?: boolean
  392. allowTaint?: boolean
  393. backgroundColor?: string
  394. foreignObjectRendering?: boolean
  395. imageTimeout?: number
  396. removeContainer?: boolean
  397. }
  398. jsPDF?: {
  399. unit?: string
  400. format?: string
  401. orientation?: string
  402. compress?: boolean
  403. }
  404. }
  405. interface PDFItem {
  406. element: HTMLElement
  407. filename: string
  408. options?: PDFOptions
  409. }
  410. const defaultPDFOptions: PDFOptions = {
  411. margin: [10, 10, 10, 10],
  412. filename: 'document.pdf',
  413. image: {
  414. type: 'jpeg',
  415. quality: 1
  416. },
  417. html2canvas: {
  418. scale: 0.8,
  419. // useCORS: true,
  420. // logging: false,
  421. // letterRendering: true,
  422. // allowTaint: true,
  423. // backgroundColor: '#ffffff',
  424. // foreignObjectRendering: false,
  425. // imageTimeout: 5000,
  426. // removeContainer: true
  427. },
  428. jsPDF: {
  429. unit: 'mm',
  430. format: 'a4',
  431. orientation: 'portrait',
  432. compress: true
  433. }
  434. }
  435. const fixInputStyles = (element: HTMLElement) => {
  436. const clone = element.cloneNode(true) as HTMLElement
  437. const inputs = clone.querySelectorAll('input, textarea, select')
  438. inputs.forEach(input => {
  439. const tagName = input.tagName.toLowerCase()
  440. const parent = input.parentElement
  441. if (!parent) return
  442. if (tagName === 'textarea') {
  443. const textarea = input as HTMLTextAreaElement
  444. const div = document.createElement('div')
  445. div.className = ''
  446. div.style.border = '1px solid #dcdfe6'
  447. div.style.backgroundColor = '#ffffff'
  448. div.style.padding = '8px'
  449. div.style.borderRadius = '4px'
  450. div.style.color = '#606266'
  451. div.style.minHeight = textarea.style.minHeight || '80px'
  452. div.style.whiteSpace = 'pre-wrap'
  453. div.style.wordWrap = 'break-word'
  454. div.style.overflow = 'visible'
  455. div.style.boxSizing = 'border-box'
  456. div.style.display = 'block'
  457. if (textarea.style.width && textarea.style.width !== 'auto') {
  458. div.style.width = textarea.style.width
  459. }
  460. div.textContent = textarea.value || textarea.textContent || ''
  461. parent.replaceChild(div, textarea)
  462. } else if (tagName === 'input') {
  463. const inputEl = input as HTMLInputElement
  464. const inputType = inputEl.type
  465. if (inputType === 'checkbox' || inputType === 'radio') {
  466. inputEl.style.opacity = '1'
  467. inputEl.style.display = 'inline-block'
  468. inputEl.style.backgroundColor = 'transparent'
  469. } else {
  470. const span = document.createElement('span')
  471. span.className = ''
  472. span.style.border = '1px solid #dcdfe6'
  473. span.style.backgroundColor = '#ffffff'
  474. span.style.padding = '4px'
  475. span.style.borderRadius = '4px'
  476. span.style.color = '#606266'
  477. span.style.display = 'inline-block'
  478. span.style.minWidth = '82px'
  479. span.style.minHeight = '30px'
  480. span.style.boxSizing = 'border-box'
  481. if (inputEl.style.width && inputEl.style.width !== 'auto') {
  482. span.style.width = inputEl.style.width
  483. }
  484. span.style.lineHeight = '30px'
  485. span.style.verticalAlign = 'middle'
  486. span.style.overflow = 'hidden'
  487. span.style.textOverflow = 'ellipsis'
  488. span.style.whiteSpace = 'nowrap'
  489. span.textContent = inputEl.value || inputEl.placeholder || ''
  490. parent.replaceChild(span, inputEl)
  491. }
  492. } else if (tagName === 'select') {
  493. const selectEl = input as HTMLSelectElement
  494. const span = document.createElement('span')
  495. span.className = ''
  496. span.style.border = '1px solid #dcdfe6'
  497. span.style.backgroundColor = '#ffffff'
  498. span.style.padding = '8px'
  499. span.style.borderRadius = '4px'
  500. span.style.color = '#606266'
  501. span.style.display = 'inline-block'
  502. span.style.minWidth = '100px'
  503. span.style.minHeight = '32px'
  504. span.style.boxSizing = 'border-box'
  505. if (selectEl.style.width && selectEl.style.width !== 'auto') {
  506. span.style.width = selectEl.style.width
  507. }
  508. span.style.lineHeight = '32px'
  509. span.style.verticalAlign = 'middle'
  510. span.style.overflow = 'hidden'
  511. span.style.textOverflow = 'ellipsis'
  512. span.style.whiteSpace = 'nowrap'
  513. const selectedOption = selectEl.options[selectEl.selectedIndex]
  514. span.textContent = selectedOption ? selectedOption.text : ''
  515. parent.replaceChild(span, selectEl)
  516. }
  517. })
  518. const allElements = clone.querySelectorAll('*')
  519. // allElements.forEach(el => {
  520. // const element = el as HTMLElement
  521. // element.style.setProperty('background-color', '#ffffff', 'important')
  522. // element.style.setProperty('background', '#ffffff', 'important')
  523. // element.style.setProperty('box-shadow', 'none', 'important')
  524. // })
  525. const elInputs = clone.querySelectorAll('.el-input__inner, .el-textarea__inner')
  526. elInputs.forEach(input => {
  527. const element = input as HTMLElement
  528. const parent = element.parentElement
  529. if (!parent) return
  530. if (element.classList.contains('el-textarea__inner')) {
  531. const div = document.createElement('div')
  532. div.className = ''
  533. div.style.border = '1px solid #dcdfe6'
  534. div.style.backgroundColor = '#ffffff'
  535. div.style.padding = '8px'
  536. div.style.borderRadius = '4px'
  537. div.style.color = '#606266'
  538. div.style.minHeight = element.style.minHeight || '80px'
  539. div.style.whiteSpace = 'pre-wrap'
  540. div.style.wordWrap = 'break-word'
  541. div.style.overflow = 'visible'
  542. div.style.boxSizing = 'border-box'
  543. div.style.display = 'block'
  544. if (element.style.width && element.style.width !== 'auto') {
  545. div.style.width = element.style.width
  546. }
  547. div.textContent = element.textContent || ''
  548. parent.replaceChild(div, element)
  549. } else {
  550. element.style.border = '1px solid #dcdfe6'
  551. element.style.backgroundColor = '#ffffff'
  552. element.style.color = '#606266'
  553. element.style.boxSizing = 'border-box'
  554. }
  555. })
  556. const elWrappers = clone.querySelectorAll('.el-input, .el-input__wrapper, .el-textarea')
  557. elWrappers.forEach(wrapper => {
  558. const element = wrapper as HTMLElement
  559. element.style.setProperty('background-color', 'transparent', 'important')
  560. element.style.setProperty('background', 'transparent', 'important')
  561. element.style.setProperty('border', 'none', 'important')
  562. element.style.setProperty('box-shadow', 'none', 'important')
  563. element.style.setProperty('padding', '0', 'important')
  564. element.style.setProperty('box-sizing', 'border-box', 'important')
  565. })
  566. return clone
  567. }
  568. const generateSinglePDF = async (item: PDFItem): Promise<{ filename: string, blob: Blob }> => {
  569. const options = { ...defaultPDFOptions, ...item.options }
  570. //fixInputStyles(item.element)
  571. const html2pdf = await getHtml2pdf()
  572. const pdfBlob = await new Promise<Blob>((resolve, reject) => {
  573. html2pdf()
  574. .set(options)
  575. .from(item.element)
  576. .toPdf()
  577. .output('blob')
  578. .then(resolve)
  579. .catch(reject)
  580. })
  581. return {
  582. filename: item.filename.endsWith('.pdf') ? item.filename : `${item.filename}.pdf`,
  583. blob: pdfBlob
  584. }
  585. }
  586. interface BatchPDFProcessOptions<T> {
  587. dataList: T[]
  588. elementId: string
  589. processItem: (item: T, index: number) => Promise<void>
  590. getFilename: (item: T, index: number) => string
  591. zipFileName: string
  592. pdfOptions?: PDFOptions
  593. onProgress?: (current: number, total: number) => void
  594. }
  595. export const batchGeneratePDFsAndZipWithProcess = async <T>(
  596. options: BatchPDFProcessOptions<T>
  597. ): Promise<void> => {
  598. const { dataList, elementId, processItem, getFilename, zipFileName, pdfOptions, onProgress } = options
  599. try {
  600. const JSZip = await getJSZip()
  601. const saveAs = await getSaveAs()
  602. const html2pdf = await getHtml2pdf()
  603. const zip = new JSZip()
  604. const pdfResults: { filename: string; blob: Blob }[] = []
  605. for (let i = 0; i < dataList.length; i++) {
  606. const item = dataList[i]
  607. try {
  608. await processItem(item, i)
  609. await nextTick()
  610. const element = document.getElementById(elementId)
  611. if (!element) {
  612. console.warn(`找不到元素: ${elementId}`)
  613. continue
  614. }
  615. const clonedElement = fixInputStyles(element)
  616. const filename = getFilename(item, i)
  617. const mergedOptions = { ...defaultPDFOptions, ...pdfOptions }
  618. const pdfBlob = await new Promise<Blob>((resolve, reject) => {
  619. html2pdf()
  620. .set(mergedOptions)
  621. .from(clonedElement)
  622. .toPdf()
  623. .output('blob')
  624. .then(resolve)
  625. .catch(reject)
  626. })
  627. pdfResults.push({
  628. filename: filename.endsWith('.pdf') ? filename : `${filename}.pdf`,
  629. blob: pdfBlob
  630. })
  631. if (onProgress) {
  632. onProgress(i + 1, dataList.length)
  633. }
  634. } catch (error) {
  635. console.error(`处理第 ${i + 1} 项时出错:`, error)
  636. }
  637. }
  638. pdfResults.forEach(({ filename, blob }) => {
  639. zip.file(filename, blob)
  640. })
  641. const zipBlob = await zip.generateAsync({
  642. type: 'blob',
  643. compression: 'DEFLATE',
  644. compressionOptions: { level: 6 }
  645. })
  646. saveAs(zipBlob, zipFileName.endsWith('.zip') ? zipFileName : `${zipFileName}.zip`)
  647. console.log(`成功生成 ${pdfResults.length} 个 PDF 文件并打包为 ${zipFileName}`)
  648. } catch (error) {
  649. console.error('批量生成 PDF 并打包失败:', error)
  650. throw error
  651. }
  652. }
  653. export const generatePDFBlob = async (
  654. element: HTMLElement,
  655. options?: PDFOptions
  656. ): Promise<Blob> => {
  657. const mergedOptions = { ...defaultPDFOptions, ...options }
  658. const html2pdf = await getHtml2pdf()
  659. //fixInputStyles(element)
  660. return new Promise((resolve, reject) => {
  661. html2pdf()
  662. .set(mergedOptions)
  663. .from(element)
  664. .toPdf()
  665. .output('blob')
  666. .then(resolve)
  667. .catch(reject)
  668. })
  669. }
  670. export const downloadAsZip = async (
  671. pdfBlob: Blob,
  672. fileName: string = 'document.pdf',
  673. zipFileName: string = 'document.zip'
  674. ): Promise<void> => {
  675. try {
  676. const JSZip = await getJSZip()
  677. const saveAs = await getSaveAs()
  678. const zip = new JSZip()
  679. const pdfFileName = fileName.endsWith('.pdf') ? fileName : `${fileName}.pdf`
  680. zip.file(pdfFileName, pdfBlob)
  681. const zipBlob = await zip.generateAsync({
  682. type: 'blob',
  683. compression: 'DEFLATE',
  684. compressionOptions: { level: 6 }
  685. })
  686. const finalZipName = zipFileName.endsWith('.zip') ? zipFileName : `${zipFileName}.zip`
  687. saveAs(zipBlob, finalZipName)
  688. console.log('压缩文件下载成功')
  689. } catch (error) {
  690. console.error('压缩文件下载失败:', error)
  691. throw error
  692. }
  693. }