PrintDialog.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <script setup lang="ts">
  2. import * as ProcessInstanceApi from '@/api/bpm/processInstance'
  3. import { useUserStore } from '@/store/modules/user'
  4. import { formatDate } from '@/utils/formatTime'
  5. import { DICT_TYPE, getDictLabel } from '@/utils/dict'
  6. import { decodeFields } from '@/utils/formCreate'
  7. const userStore = useUserStore()
  8. const visible = ref(false)
  9. const loading = ref(false)
  10. const printData = ref()
  11. const userName = computed(() => userStore.user.nickname ?? '')
  12. const printTime = ref(formatDate(new Date(), 'YYYY-MM-DD HH:mm'))
  13. const formFields = ref()
  14. const printDataMap = ref({})
  15. const open = async (id: string) => {
  16. loading.value = true
  17. try {
  18. printData.value = await ProcessInstanceApi.getProcessInstancePrintData(id)
  19. initPrintDataMap()
  20. parseFormFields()
  21. } finally {
  22. loading.value = false
  23. }
  24. visible.value = true
  25. }
  26. defineExpose({ open })
  27. const parseFormFields = () => {
  28. if (!printData.value) return
  29. const formFieldsObj = decodeFields(
  30. printData.value.processInstance.processDefinition?.formFields || []
  31. )
  32. const processVariables = printData.value.processInstance.formVariables
  33. let res: any = []
  34. for (const item of formFieldsObj) {
  35. const id = item['field']
  36. const name = item['title']
  37. const variable = processVariables[item['field']]
  38. let html = variable
  39. switch (item['type']) {
  40. case 'UploadImg': {
  41. let imgEl = document.createElement('img')
  42. imgEl.setAttribute('src', variable)
  43. imgEl.setAttribute('style', 'max-width: 600px;')
  44. html = imgEl.outerHTML
  45. break
  46. }
  47. case 'radio':
  48. case 'checkbox':
  49. case 'select': {
  50. const options = item['options'] || []
  51. const temp: any = []
  52. if (Array.isArray(variable)) {
  53. const labels = options.filter((o) => variable.includes(o.value)).map((o) => o.label)
  54. temp.push(...labels)
  55. } else {
  56. const opt = options.find((o) => o.value === variable)
  57. temp.push(opt.label)
  58. }
  59. html = temp.join(',')
  60. }
  61. // TODO 更多表单打印展示
  62. }
  63. printDataMap.value[item['field']] = html
  64. res.push({ id, name, html })
  65. }
  66. formFields.value = res
  67. }
  68. const initPrintDataMap = () => {
  69. printDataMap.value['startUser'] = printData.value.processInstance.startUser.nickname
  70. printDataMap.value['startUserDept'] = printData.value.processInstance.startUser.deptName
  71. printDataMap.value['processName'] = printData.value.processInstance.name
  72. printDataMap.value['processNum'] = printData.value.processInstance.id
  73. printDataMap.value['startTime'] = formatDate(printData.value.processInstance.startTime)
  74. printDataMap.value['endTime'] = formatDate(printData.value.processInstance.endTime)
  75. printDataMap.value['processStatus'] = getDictLabel(
  76. DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
  77. printData.value.processInstance.status
  78. )
  79. printDataMap.value['printUser'] = userName.value
  80. printDataMap.value['printTime'] = printTime.value
  81. }
  82. const getPrintTemplateHTML = () => {
  83. const parser = new DOMParser()
  84. let doc = parser.parseFromString(printData.value.printTemplateHtml, 'text/html')
  85. // table 添加border
  86. let tables = doc.querySelectorAll('table')
  87. tables.forEach((item) => {
  88. item.setAttribute('border', '1')
  89. item.setAttribute('style', (item.getAttribute('style') || '') + 'border-collapse:collapse;')
  90. })
  91. // 替换 mentions
  92. let mentions = doc.querySelectorAll('[data-w-e-type="mention"]')
  93. mentions.forEach((item) => {
  94. const mentionId = JSON.parse(decodeURIComponent(item.getAttribute('data-info') ?? ''))['id']
  95. item.innerHTML = printDataMap.value[mentionId] ?? ''
  96. })
  97. // 替换流程记录
  98. let processRecords = doc.querySelectorAll('[data-w-e-type="process-record"]')
  99. let processRecordTable: Element = document.createElement('table')
  100. if (processRecords.length > 0) {
  101. // 构建流程记录html
  102. processRecordTable.setAttribute('border', '1')
  103. processRecordTable.setAttribute('style', 'width:100%;border-collapse:collapse;')
  104. const headTr = document.createElement('tr')
  105. const headTd = document.createElement('td')
  106. headTd.setAttribute('colspan', '2')
  107. headTd.setAttribute('width', 'auto')
  108. headTd.setAttribute('style', 'text-align: center;')
  109. headTd.innerHTML = '流程节点'
  110. headTr.appendChild(headTd)
  111. processRecordTable.appendChild(headTr)
  112. printData.value.tasks.forEach((item) => {
  113. const tr = document.createElement('tr')
  114. const td1 = document.createElement('td')
  115. td1.innerHTML = item.name
  116. const td2 = document.createElement('td')
  117. td2.innerHTML = item.description
  118. tr.appendChild(td1)
  119. tr.appendChild(td2)
  120. processRecordTable.appendChild(tr)
  121. })
  122. }
  123. processRecords.forEach((item) => {
  124. item.innerHTML = processRecordTable.outerHTML
  125. })
  126. // 返回 html
  127. return doc.body.innerHTML
  128. }
  129. const printObj = ref({
  130. id: 'printDivTag',
  131. popTitle: '&nbsp',
  132. extraCss: '/print.css',
  133. extraHead: '',
  134. zIndex: 20003
  135. })
  136. </script>
  137. <template>
  138. <el-dialog v-loading="loading" v-model="visible" :show-close="false">
  139. <div id="printDivTag" style="word-break: break-all">
  140. <div v-if="printData.printTemplateEnable" v-html="getPrintTemplateHTML()"></div>
  141. <div v-else>
  142. <h2 class="text-center">{{ printData.processInstance.name }}</h2>
  143. <div class="text-right text-15px">{{ '打印人员: ' + userName }}</div>
  144. <div class="flex justify-between">
  145. <div class="text-15px">{{ '流程编号: ' + printData.processInstance.id }}</div>
  146. <div class="text-15px">{{ '打印时间: ' + printTime }}</div>
  147. </div>
  148. <table class="mt-20px w-100%" border="1" style="border-collapse: collapse">
  149. <tbody>
  150. <tr>
  151. <td class="p-5px w-25%">发起人</td>
  152. <td class="p-5px w-25%">{{ printData.processInstance.startUser.nickname }}</td>
  153. <td class="p-5px w-25%">发起时间</td>
  154. <td class="p-5px w-25%">{{ formatDate(printData.processInstance.startTime) }}</td>
  155. </tr>
  156. <tr>
  157. <td class="p-5px w-25%">所属部门</td>
  158. <td class="p-5px w-25%">{{ printData.processInstance.startUser.deptName }}</td>
  159. <td class="p-5px w-25%">流程状态</td>
  160. <td class="p-5px w-25%">
  161. {{
  162. getDictLabel(
  163. DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
  164. printData.processInstance.status
  165. )
  166. }}
  167. </td>
  168. </tr>
  169. <tr>
  170. <td class="p-5px w-100% text-center" colspan="4">
  171. <h4>表单内容</h4>
  172. </td>
  173. </tr>
  174. <tr v-for="item in formFields" :key="item.id">
  175. <td class="p-5px w-20%">
  176. {{ item.name }}
  177. </td>
  178. <td class="p-5px w-80%" colspan="3">
  179. <div v-html="item.html"></div>
  180. </td>
  181. </tr>
  182. <tr>
  183. <td class="p-5px w-100% text-center" colspan="4">
  184. <h4>流程节点</h4>
  185. </td>
  186. </tr>
  187. <tr v-for="item in printData.tasks" :key="item.id">
  188. <td class="p-5px w-20%">
  189. {{ item.name }}
  190. </td>
  191. <td class="p-5px w-80%" colspan="3">
  192. {{ item.description }}
  193. <div v-if="item.signPicUrl && item.signPicUrl.length > 0">
  194. <img class="w-90px h-40px" :src="item.signPicUrl" alt="" />
  195. </div>
  196. </td>
  197. </tr>
  198. </tbody>
  199. </table>
  200. </div>
  201. </div>
  202. <template #footer>
  203. <div class="dialog-footer">
  204. <el-button @click="visible = false">取 消</el-button>
  205. <el-button type="primary" v-print="printObj"> 打 印</el-button>
  206. </div>
  207. </template>
  208. </el-dialog>
  209. </template>
  210. <style>
  211. /* 修复打印只显示一页 */
  212. @media print {
  213. @page {
  214. size: auto;
  215. }
  216. body,
  217. html,
  218. div {
  219. height: auto !important;
  220. }
  221. }
  222. </style>