HcPdfSign.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. import { getArrValue, getRandom, isNullES, isNumber, isString } from 'js-fast-way'
  2. //PDF签章
  3. export default class HcPdfSign {
  4. static iframeDom = null
  5. static pdfViewer = null
  6. static signImgCss = {}
  7. static signType = ''
  8. static disX = 0
  9. static disY = 0
  10. static curSignDom = null
  11. static signList = []
  12. static signChange = null
  13. static pdfLoadFunc = null
  14. static batchSign = false
  15. static signNum = 0
  16. /**
  17. * 初始化创建PDF预览
  18. * @param ele 元素的ref,或id值
  19. * @param url pdf的url
  20. * @param img 签章图片url
  21. * @param func 回调函数
  22. */
  23. static createPdf({ ele, url, img, change, load }) {
  24. this.initVarNull()
  25. //判断参数是否合法
  26. if (isNullES(ele)) {
  27. console.warn('请传入参数,必须是,ref的dom元素,或id值')
  28. return false
  29. }
  30. //是否为字符串或数值类型
  31. let dom = null, errTip = 'ele参数不合法,必须是ref的dom元素,或id值'
  32. if (isString(ele) || isNumber(ele)) {
  33. try {
  34. dom = document.getElementById(ele)
  35. } catch (e) {
  36. console.error(errTip)
  37. return false
  38. }
  39. } else {
  40. try {
  41. ele.cloneNode(true)
  42. if (ele.nodeType === 1 || ele.nodeType === 9) {
  43. dom = ele
  44. } else {
  45. console.error(errTip)
  46. return false
  47. }
  48. } catch (e) {
  49. console.error(errTip)
  50. return false
  51. }
  52. }
  53. //清空dom
  54. const children = dom.children
  55. if (children.length > 0) {
  56. for (let i = children.length - 1; i >= 0; i--) {
  57. dom.removeChild(children[i])
  58. }
  59. }
  60. if (isNullES(url)) {
  61. console.warn('请传入Pdf的url参数')
  62. return false
  63. }
  64. //创建iframe
  65. const iframe = document.createElement('iframe')
  66. iframe.setAttribute('id', 'pdf-sign-' + getRandom(6))
  67. iframe.src = `/plugins/pdfjs/web/viewer.html?file=${url}#zoom=100`
  68. iframe.style.width = '100%'
  69. iframe.style.height = '100%'
  70. iframe.style.border = '1px solid #ccc'
  71. iframe.name = 'HcPdfSign'
  72. iframe.addEventListener('load', () => {
  73. this.iframeLoad()
  74. })
  75. dom.appendChild(iframe)
  76. this.iframeDom = iframe
  77. //设置签章图片
  78. if (isNullES(img)) {
  79. console.warn('请传入签章图片')
  80. return false
  81. }
  82. this.signImage(img)
  83. //设置回调事件
  84. if (!isNullES(change)) {
  85. this.addEventFunc(change)
  86. }
  87. //设置回调事件
  88. if (!isNullES(load)) {
  89. this.addPdfLoadFunc(load)
  90. }
  91. }
  92. //初始化空值
  93. static initVarNull() {
  94. this.iframeDom = null
  95. this.pdfViewer = null
  96. this.signImgCss = {}
  97. this.signType = ''
  98. this.disX = 0
  99. this.disY = 0
  100. this.curSignDom = null
  101. this.signList = []
  102. this.signChange = null
  103. this.pdfLoadFunc = null
  104. this.batchSign = false
  105. this.signNum = 0
  106. }
  107. //设置回调事件
  108. static addEventFunc(func) {
  109. if (typeof func !== 'function') {
  110. console.warn('请传入函数')
  111. return false
  112. } else {
  113. this.signChange = func
  114. }
  115. }
  116. //触发回调事件
  117. static signChangeFunc() {
  118. if (typeof this.signChange !== 'function') {
  119. return false
  120. } else {
  121. this.signChange(this.signList)
  122. }
  123. }
  124. //设置加载完成的回调事件
  125. static addPdfLoadFunc(func) {
  126. if (typeof func !== 'function') {
  127. console.warn('请传入函数')
  128. return false
  129. } else {
  130. this.pdfLoadFunc = func
  131. }
  132. }
  133. //加载完成
  134. static setPdfLoadFunc(val) {
  135. if (typeof this.pdfLoadFunc !== 'function') {
  136. return false
  137. } else {
  138. this.pdfLoadFunc(val)
  139. }
  140. }
  141. //设置签章图片
  142. static signImage(url) {
  143. this.signImgCss = { url: url, width: 100, height: 26 }
  144. }
  145. //创建签章图片
  146. static async createSignImage(event) {
  147. //创建图片元素
  148. const { url, width, height } = this.signImgCss
  149. const uuid = 'pdf-sign-img-' + getRandom(6)
  150. const signImg = document.createElement('img')
  151. signImg.setAttribute('id', uuid)
  152. signImg.src = url
  153. signImg.style.position = 'absolute'
  154. signImg.style.width = width + 'px'
  155. signImg.style.height = height + 'px'
  156. signImg.style.left = (event.layerX - (width / 2)) + 'px'
  157. signImg.style.top = (event.layerY - (height / 2)) + 'px'
  158. signImg.style.pointerEvents = 'auto'
  159. signImg.style.zIndex = 999
  160. return signImg
  161. }
  162. /**
  163. * 是否批量签章
  164. * @param val true/false
  165. */
  166. static setBatchSign(val = false) {
  167. this.batchSign = val
  168. }
  169. //让PDF页面全部渲染一下
  170. static async setPageScrolling() {
  171. const pageDom = this.pdfViewer?.children ?? []
  172. await this.toPdfPage('firstPage')
  173. for (let i = 0; i < pageDom.length; i++) {
  174. await this.toPdfPage('next')
  175. }
  176. await this.toPdfPage('firstPage')
  177. }
  178. /**
  179. * 跳转pdf的页面
  180. * @param pageId 最后一页:lastPage,第一页:firstPage,上一页:previous,下一页:next
  181. */
  182. static async toPdfPage(pageId) {
  183. return new Promise((resolve) => {
  184. this.iframeDom?.contentDocument?.getElementById(pageId)?.click()
  185. setTimeout(() => {
  186. resolve(true)
  187. }, 100)
  188. })
  189. }
  190. //PDF加载完成
  191. static iframeLoad() {
  192. const viewer = this.iframeDom?.contentDocument?.getElementById('viewer')
  193. //页面被点击
  194. viewer.addEventListener('click', (event) => {
  195. this.viewerClick(event).then()
  196. })
  197. //鼠标移动
  198. viewer.addEventListener('mousemove', (event) => {
  199. event.preventDefault()
  200. if (this.signType !== '移动' || !this.curSignDom) {
  201. return
  202. }
  203. //鼠标位置
  204. const left = (event.clientX - this.disX) + 'px'
  205. const top = (event.clientY - this.disY) + 'px'
  206. this.curSignDom.style.left = left
  207. this.curSignDom.style.top = top
  208. //批量处理
  209. if (this.batchSign) {
  210. const curDom = this.curSignDom
  211. let { data: curSign } = this.getSignImgDom(curDom)
  212. if (!curSign) return
  213. const newArr = this.signList.filter(item => {
  214. return item.type === curSign.type
  215. })
  216. newArr.forEach(item => {
  217. item.dom.style.left = left
  218. item.dom.style.top = top
  219. })
  220. }
  221. })
  222. //鼠标抬起
  223. viewer.addEventListener('mouseup', () => {
  224. if (!this.curSignDom) return
  225. this.signType = '签名'
  226. this.curSignDom.style.cursor = 'default'
  227. const curDom = this.curSignDom
  228. //获取数据
  229. let { data: curSign } = this.getSignImgDom(curDom)
  230. if (!curSign) return
  231. //计算坐标
  232. const left = curDom.style.left.replace('px', '')
  233. const top = curDom.style.top.replace('px', '')
  234. const { width, height } = this.signImgCss
  235. const lefts = Number(left) + Number(width / 2)
  236. const tops = Number(top) + Number(height / 2)
  237. const lx = ((lefts * 100) / curDom.parentNode.clientWidth).toFixed(4)
  238. const ly = ((tops * 100) / curDom.parentNode.clientHeight).toFixed(4)
  239. //批量处理
  240. if (this.batchSign) {
  241. const newArr = this.signList.filter(item => {
  242. return item.type === curSign.type
  243. })
  244. newArr.forEach(item => {
  245. item.lx = lx
  246. item.ly = ly
  247. })
  248. } else {
  249. curSign.lx = lx
  250. curSign.ly = ly
  251. }
  252. this.signChangeFunc()
  253. })
  254. //监听按键
  255. const container = this.iframeDom?.contentDocument?.getElementById('outerContainer')
  256. container.addEventListener('keyup', (event) => {
  257. //删除签章
  258. if (event.keyCode === 8 && event.key === 'Backspace') {
  259. const curDom = this.curSignDom
  260. const { index } = this.getSignImgDom(curDom)
  261. if (index <= -1) return
  262. this.delSignImg(index).then()
  263. }
  264. })
  265. this.pdfViewer = viewer
  266. //处理pdf渲染
  267. if (this.batchSign) {
  268. setTimeout(async () => {
  269. await this.setPageScrolling()
  270. this.setPdfLoadFunc(true)
  271. }, 500)
  272. } else {
  273. setTimeout(async () => {
  274. this.setPdfLoadFunc(true)
  275. }, 100)
  276. }
  277. }
  278. //删除签章
  279. static async delSignImg(index) {
  280. if (index <= -1) return
  281. if (this.batchSign) {
  282. const list = this.signList, curDom = list[index]
  283. //倒序删除签章
  284. for (let i = list.length - 1; i >= 0; i--) {
  285. if (list[i].type === curDom.type) {
  286. list[i].dom.remove()
  287. delete list[i].dom
  288. list.splice(i, 1)
  289. }
  290. }
  291. this.signList = list
  292. this.signChangeFunc()
  293. } else {
  294. this.signList[index].dom.remove()
  295. this.signList.splice(index, 1)
  296. this.signChangeFunc()
  297. }
  298. }
  299. //获取签章图片的数据
  300. static getSignImgDom(curDom) {
  301. if (isNullES(curDom)) {
  302. return { data: null, index: -1 }
  303. }
  304. //获取数据
  305. const id = curDom?.getAttribute('id')
  306. let curSign = null, index = -1
  307. for (let i = 0; i < this.signList.length; i++) {
  308. const item = this.signList[i]
  309. if (item.id === id) {
  310. curSign = item
  311. index = i
  312. break
  313. }
  314. }
  315. return { data: curSign, index }
  316. }
  317. //页面被点击
  318. static async viewerClick(event) {
  319. const target = event.target
  320. //当前为移动模式
  321. if (this.signType === '移动') {
  322. return
  323. }
  324. //不在PDF页面上点击
  325. if (target.className !== 'textLayer') {
  326. return
  327. }
  328. //批量签章
  329. this.signNum ++
  330. if (this.batchSign) {
  331. const parent = target?.parentNode?.parentNode?.children ?? []
  332. for (let i = 0; i < parent.length; i++) {
  333. await this.setPdfNodeSign(parent[i], parent[i]?.children[1], event)
  334. }
  335. this.signChangeFunc()
  336. } else {
  337. await this.setPdfNodeSign(target?.parentNode, target, event)
  338. this.signChangeFunc()
  339. }
  340. }
  341. //设置PDF节点签章
  342. static async setPdfNodeSign(node, textLayer, event) {
  343. if (isNullES(node) || isNullES(textLayer) || isNullES(event)) {
  344. return
  345. }
  346. if (node.children.length < 3) {
  347. node.prepend('<div class="signLayer" style="position: absolute;height: 100%;width: 100%;z-index: 999;pointer-events: none;overflow: hidden"></div>')
  348. }
  349. //插入图片
  350. const signImgDom = await this.createSignImage(event)
  351. node.children[0].append(signImgDom)
  352. //鼠标按下
  353. signImgDom.addEventListener('mousedown', (e) => {
  354. //新的当前选中项
  355. this.curSignDom = e.target
  356. this.signType = '移动'
  357. e.target.style.cursor = 'move'
  358. //鼠标相对于图片的位置
  359. this.disX = e.clientX - e.target.offsetLeft
  360. this.disY = e.clientY - e.target.offsetTop
  361. })
  362. //保存签章信息
  363. this.signList.push({
  364. type: this.signNum,
  365. url: this.signImgCss?.url ?? '',
  366. page: node.getAttribute('data-page-number'),
  367. id: signImgDom.getAttribute('id'),
  368. lx: ((event.layerX * 100) / textLayer.clientWidth).toFixed(4),
  369. ly: ((event.layerY * 100) / textLayer.clientHeight).toFixed(4),
  370. dom: signImgDom,
  371. })
  372. return signImgDom
  373. }
  374. //设置PDF签章图片
  375. static async setPdfSignImg(arr) {
  376. const list = getArrValue(arr)
  377. if (list.length <= 0) return
  378. const pageDom = this.pdfViewer?.children ?? []
  379. for (let i = 0; i < list.length; i++) {
  380. const page = Number(list[i].page) - 1
  381. const dom = pageDom[page].getElementsByClassName('canvasWrapper')[0]
  382. //创建图片元素
  383. const signImg = document.createElement('img')
  384. signImg.setAttribute('id', list[i].id)
  385. signImg.src = list[i].url
  386. signImg.style.position = 'absolute'
  387. signImg.style.width = list[i].dom.style.width
  388. signImg.style.height = list[i].dom.style.height
  389. signImg.style.left = list[i].dom.style.left
  390. signImg.style.top = list[i].dom.style.top
  391. signImg.style.pointerEvents = 'auto'
  392. signImg.style.zIndex = 999
  393. dom.append(signImg)
  394. //鼠标按下
  395. signImg.addEventListener('mousedown', (e) => {
  396. //新的当前选中项
  397. this.curSignDom = e.target
  398. this.signType = '移动'
  399. e.target.style.cursor = 'move'
  400. //鼠标相对于图片的位置
  401. this.disX = e.clientX - e.target.offsetLeft
  402. this.disY = e.clientY - e.target.offsetTop
  403. })
  404. //保存签章信息
  405. this.signList.push({
  406. ...list[i],
  407. dom: signImg,
  408. })
  409. }
  410. }
  411. }