pdfViewModal.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <template>
  2. <a-modal
  3. centered
  4. v-drag-modal
  5. class="common-pdfView-modal"
  6. :footer="null"
  7. :maskClosable="false"
  8. v-model="isShow"
  9. :title="modalTit"
  10. :zIndex="1001"
  11. :destroyOnClose="true"
  12. @cancel="handleCommonCancel"
  13. width="1000px">
  14. <a-spin :spinning="spinning" tip="Loading...">
  15. <div class="common-main">
  16. <div id="canvas"></div>
  17. <div class="tools">
  18. <div>
  19. 调整比例:{{ Math.round(scalVal*100) }}%
  20. <div>
  21. <a-button :disabled="scalLoading" style="margin-right: 10px;" size="small" @click="setScalVal(0)"><a-icon type="minus" /> 缩小</a-button>
  22. <a-button :disabled="scalLoading" size="small" @click="setScalVal(1)"><a-icon type="plus" /> 放大</a-button>
  23. </div>
  24. </div>
  25. <div>
  26. <div v-if="hasClipboard">
  27. <a-button :disabled="scalLoading" size="small" @click="handleCapture('export')"><a-icon type="export" /> 导出图片</a-button>
  28. </div>
  29. </div>
  30. <div>
  31. 选择打印机:
  32. <div>
  33. <a-select size="small" style="width:200px;" v-model="printIndex" @change="changePrint" placeholder="选择打印机">
  34. <a-select-option v-for="item in printList" :key="'print-'+item.value" :value="item.name">
  35. {{ item.name }}
  36. </a-select-option>
  37. </a-select>
  38. </div>
  39. </div>
  40. </div>
  41. <vue-scroll :ops="ops" :style="{width: '100%',height: '70vh',margin:'0 auto',padding: '10px 0'}">
  42. <div v-if="loadedRatio > 0 && loadedRatio < 1" style="background-color: green; color: white; text-align: center" :style="{ width: loadedRatio * 100 + '%' }">{{ Math.floor(loadedRatio * 100) }}%</div>
  43. <pdf
  44. ref="pdf"
  45. v-for="(item,index) in pdfList"
  46. :key="'pdf-'+index"
  47. :style="{width: Math.round(scalVal*100)+'%',margin:'0 auto'}"
  48. :src="getUrl('data:application/pdf;base64,' + item)"
  49. :page="page"
  50. :rotate="rotate"
  51. @error="error"
  52. ></pdf>
  53. </vue-scroll>
  54. </div>
  55. <div class="btn-box">
  56. <a-button size="large" style="margin-right: 25px;" @click="handleCommonCancel" v-if="isCancel">{{ cancelText }}</a-button>
  57. <a-button size="large" style="margin-right: 25px;" type="default" @click="handleCapture(hasClipboard?'copy':'export')">
  58. {{ hasClipboard?'复制截图':'导出图片' }}
  59. </a-button>
  60. <a-button size="large" type="primary" :loading="$store.state.app.printLoading" @click="handleCommonOk">
  61. {{ $store.state.app.printLoading?'打印中...':okText }}
  62. </a-button>
  63. </div>
  64. </a-spin>
  65. </a-modal>
  66. </template>
  67. <script>
  68. import moment from 'moment'
  69. import pdf from 'vue-pdf-signature'
  70. import CMapReaderFactory from 'vue-pdf-signature/src/CMapReaderFactory.js'
  71. import { pdfPrint, getPrintList, hasExitTaskByName } from '@/libs/JGPrint'
  72. export default {
  73. components: {
  74. pdf
  75. },
  76. name: 'PdfViewModal',
  77. props: {
  78. openModal: { // 弹框显示状态
  79. type: Boolean,
  80. default: false
  81. },
  82. modalTit: { // 弹框标题
  83. type: String,
  84. default: '打印预览'
  85. },
  86. okText: { // 确定 按钮文字
  87. type: String,
  88. default: '打印'
  89. },
  90. cancelText: { // 取消 按钮文字
  91. type: String,
  92. default: '取消'
  93. },
  94. isCancel: { // 是否显示 取消 按钮
  95. type: Boolean,
  96. default: true
  97. },
  98. spinning: { // 加载中
  99. type: Boolean,
  100. default: false
  101. }
  102. },
  103. data () {
  104. return {
  105. scalLoading: false,
  106. pdfAllLoad: 0,
  107. scalVal: 0.75,
  108. isShow: this.openModal, // 是否打开弹框
  109. show: true,
  110. pdfList: [],
  111. src: '',
  112. loadedRatio: 0,
  113. page: 1,
  114. numPages: 0,
  115. rotate: 0,
  116. curpage: 1,
  117. total: 3,
  118. ops: {
  119. vuescroll: {},
  120. scrollPanel: {},
  121. rail: {
  122. keepShow: true
  123. },
  124. bar: {
  125. hoverStyle: true,
  126. onlyShowBarOnScroll: false, // 是否只有滚动的时候才显示滚动条
  127. background: '#9d9d9d', // 滚动条颜色
  128. opacity: 0.5, // 滚动条透明度
  129. size: '10px',
  130. 'overflow-x': 'hidden'
  131. }
  132. },
  133. printIndex: undefined,
  134. printList: []
  135. }
  136. },
  137. computed: {
  138. hasClipboard () {
  139. return typeof ClipboardItem !== 'undefined'
  140. }
  141. },
  142. methods: {
  143. // 选择打印机
  144. changePrint (v) {
  145. this.printIndex = v
  146. this.$store.commit('SET_printDefNeedle', this.printIndex)
  147. this.$store.state.app.printTaskID = undefined
  148. },
  149. // 缩放
  150. setScalVal (t) {
  151. if (!this.scalLoading) {
  152. this.scalLoading = true
  153. this.scalVal = this.scalVal + (t ? 0.05 : -0.05)
  154. if (this.scalVal > 1) {
  155. this.scalVal = 1
  156. }
  157. if (this.scalVal < 0.5) {
  158. this.scalVal = 0.5
  159. }
  160. setTimeout(() => {
  161. this.scalLoading = false
  162. }, 1000)
  163. }
  164. },
  165. getUrl (item) {
  166. const src = pdf.createLoadingTask({ url: item, CMapReaderFactory })
  167. return src
  168. },
  169. setData (data) {
  170. // console.log(data)
  171. this.pdfList = data
  172. this.curpage = 1
  173. this.total = data.length
  174. // 获取打印机列表
  175. this.printList = getPrintList()
  176. const defPrint = this.$store.state.app.printDefNeedle
  177. this.printIndex = defPrint ? (defPrint == 'undefined' ? undefined : defPrint) : undefined
  178. },
  179. error: function (err) {
  180. console.log(err)
  181. },
  182. // 确定
  183. handleCommonOk () {
  184. const _this = this
  185. const hasPrint = this.printList.find(item => item.name == this.printIndex)
  186. // 已选择打印机
  187. if (hasPrint) {
  188. hasExitTaskByName(function () {
  189. pdfPrint(_this.$store.state.app.pdfPrintList, 0)
  190. })
  191. } else {
  192. this.$message.info('请选择打印机')
  193. }
  194. _this.$emit('ok')
  195. },
  196. // 取消
  197. handleCommonCancel () {
  198. this.isShow = false
  199. this.pdfList = []
  200. this.$store.commit('SET_pdfPrintList', [])
  201. this.$store.commit('SET_showPdfPrint', false)
  202. this.$emit('cancel')
  203. this.scalVal = 0.75
  204. },
  205. // 截图
  206. handleCapture (type) {
  207. if (this.scalLoading) { return }
  208. const pdf = this.$refs.pdf
  209. const len = pdf.length
  210. const imgsList = []
  211. const canvas = document.createElement('canvas')
  212. let maxHeight = 0
  213. const maxWidth = pdf[0].$refs.canvas.width
  214. for (let i = 0; i < len; i++) {
  215. const imgs = pdf[i].$refs.canvas
  216. maxHeight = maxHeight + imgs.height
  217. imgsList.push(imgs)
  218. }
  219. // 设置画布宽度和高度
  220. canvas.width = maxWidth
  221. canvas.height = maxHeight
  222. const ctx = canvas.getContext('2d')
  223. // 开始合并
  224. let y = 0
  225. imgsList.map(item => {
  226. ctx.drawImage(item, 0, y, item.width, item.height)
  227. y = y + item.height
  228. })
  229. const base64Str = canvas.toDataURL('image/png')
  230. // 复制图片
  231. if (type == 'copy') {
  232. this.copyImgBtn(base64Str)
  233. }
  234. // 下载图片
  235. if (type == 'export') {
  236. const fileName = moment().format('YYYYMMDDHHmmss')
  237. this.downloadFile(fileName, base64Str)
  238. }
  239. // 清除画布
  240. ctx.clearRect(0, 0, maxWidth, maxHeight)
  241. },
  242. downloadFile (fileName, base64Data) {
  243. const aLink = document.createElement('a')
  244. const blob = this.convertBase64ToBlob(base64Data.split(',')[1], 'image/png')
  245. const evt = document.createEvent('HTMLEvents')
  246. // initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
  247. evt.initEvent('click', true, true)
  248. aLink.download = fileName
  249. aLink.href = URL.createObjectURL(blob)
  250. aLink.click()
  251. },
  252. // 将照片复制到剪贴版
  253. copyImgBtn (base64Data) {
  254. const blobInput = this.convertBase64ToBlob(base64Data.split(',')[1], 'image/png')
  255. if (navigator.clipboard) {
  256. const clipboardItemInput = new ClipboardItem({ 'image/png': blobInput })
  257. navigator.clipboard.write([clipboardItemInput])
  258. this.$message.success('截图成功,可以右键去粘贴图片!')
  259. } else {
  260. const image = nativeImageElectron.createFromDataURL(base64Data)
  261. clipboardElectron.writeImage(image)
  262. this.$message.success('截图成功,可以右键去粘贴图片!')
  263. }
  264. },
  265. convertBase64ToBlob (base64, type) {
  266. var bytes = window.atob(base64)
  267. var ab = new ArrayBuffer(bytes.length)
  268. var ua = new Uint8Array(ab)
  269. for (var i = 0; i < bytes.length; i++) {
  270. ua[i] = bytes.charCodeAt(i)
  271. }
  272. return new Blob([ab], { type: type })
  273. }
  274. },
  275. watch: {
  276. // 父页面传过来的弹框状态
  277. openModal (newValue, oldValue) {
  278. this.isShow = newValue
  279. },
  280. // 重定义的弹框状态
  281. isShow (newValue, oldValue) {
  282. if (!newValue) {
  283. this.$emit('cancel')
  284. }
  285. }
  286. }
  287. }
  288. </script>
  289. <style lang="less">
  290. .common-pdfView-modal{
  291. .ant-modal-body{
  292. padding: 0 0 20px 0;
  293. }
  294. .common-main{
  295. background-color: #666;
  296. .tools{
  297. padding: 10px;
  298. display: flex;
  299. align-items: center;
  300. border-bottom: 1px solid #eee;
  301. background-color: #f8f8f8;
  302. justify-content: space-between;
  303. > div{
  304. display: flex;
  305. align-items: center;
  306. >div{
  307. padding: 0 10px;
  308. }
  309. }
  310. }
  311. }
  312. .btn-box{
  313. text-align: center;
  314. margin-top: 20px;
  315. }
  316. }
  317. </style>