outBedCard.ts 22 KB

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