jp-signature.uvue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. <template>
  2. <view class="l-signature" ref="signatureRef" :style="drawableStyle">
  3. <!-- #ifdef APP -->
  4. <view class="l-signature-landscape" ref="signatureLandscapeRef" v-if="landscape && url !=''"
  5. :style="landscapeStyle">
  6. <image class="l-signature-image" :style="landscapeImageStyle" :src="url"></image>
  7. </view>
  8. <!-- #endif -->
  9. <!-- #ifdef WEB -->
  10. <!-- #endif -->
  11. </view>
  12. </template>
  13. <script lang="uts" setup>
  14. // @ts-nocheck
  15. // #ifdef APP
  16. import { Signature } from './signature.uts'
  17. // #endif
  18. // #ifndef APP
  19. import { Signature } from './signature.js'
  20. // #endif
  21. import { nextTick } from 'vue'
  22. import { LSignatureToTempFilePathOptions, LSignatureToFileSuccess, LSignatureOptions } from '../../index.uts'
  23. // type SignatureToFileSuccessCallback = (res : UTSJSONObject) => void
  24. // type SignatureToFileFailCallback = (res : TakeSnapshotFail) => void
  25. // type SignatureToFileCompleteCallback = (res : any) => void
  26. /**
  27. * LimeSignature 手写板签名
  28. * @description 手写板签名插件,uvue专用版。
  29. * @tutorial https://ext.dcloud.net.cn/plugin?id=4354
  30. * @property {Number} penSize 画笔大小
  31. * @property {String} penColor 画笔颜色
  32. * @property {String} backgroundColor 背景颜色,不填则为透明
  33. * @property {Boolean} disableScroll 当在写字时,禁止屏幕滚动以及下拉刷新,nvue无效
  34. */
  35. const props = defineProps({
  36. styles: {
  37. type: String,
  38. default: ''
  39. },
  40. penColor: {
  41. type: String,
  42. default: 'black'
  43. },
  44. penSize: {
  45. type: Number,
  46. default: 2
  47. },
  48. backgroundColor: {
  49. type: String,
  50. default: ''
  51. },
  52. openSmooth: {
  53. type: Boolean,
  54. default: false
  55. },
  56. minLineWidth: {
  57. type: Number,
  58. default: 2
  59. },
  60. maxLineWidth: {
  61. type: Number,
  62. default: 6
  63. },
  64. minSpeed: {
  65. type: Number,
  66. default: 1.5
  67. },
  68. maxWidthDiffRate: {
  69. type: Number,
  70. default: 20
  71. },
  72. maxHistoryLength: {
  73. type: Number,
  74. default: 20
  75. },
  76. disableScroll: {
  77. type: Boolean,
  78. default: true
  79. },
  80. disabled: {
  81. type: Boolean,
  82. default: false
  83. },
  84. landscape: {
  85. type: Boolean,
  86. default: false
  87. },
  88. })
  89. const drawableStyle = computed<string>(() : string => {
  90. let style : string = ''
  91. if (props.backgroundColor != '') {
  92. style += `background-color: ${props.backgroundColor};`
  93. }
  94. if (props.styles != '') {
  95. style += props.styles
  96. }
  97. return style
  98. })
  99. const signatureRef = ref<UniElement | null>(null)
  100. let signatureLandscapeRef = ref<UniElement | null>(null)
  101. let landscapeStyle = ref<Map<string, string>>(new Map())
  102. let landscapeImageStyle = ref<Map<string, string>>(new Map())
  103. let signature : Signature | null = null
  104. let url = ref('')
  105. // #ifdef WEB
  106. let canvas : HTMLCanvasElement | null = null
  107. let touchstart,touchmove,touchend
  108. // #endif
  109. const clear = () => {
  110. signature?.clear()
  111. }
  112. const redo = () => {
  113. signature?.redo()
  114. }
  115. const undo = () => {
  116. signature?.undo()
  117. }
  118. const canvasToTempFilePath = (options : LSignatureToTempFilePathOptions) => {
  119. const success = options.success // as SignatureToFileSuccessCallback | null
  120. const fail = options.fail // as SignatureToFileFailCallback | null
  121. const complete = options.complete// as SignatureToFileCompleteCallback | null
  122. const format = options.format ?? 'png'
  123. // #ifdef APP
  124. signatureRef.value?.takeSnapshot({
  125. format,
  126. success: (res) => {
  127. if (props.landscape) {
  128. url.value = res.tempFilePath;
  129. setTimeout(() => {
  130. signatureLandscapeRef.value?.takeSnapshot({
  131. format,
  132. success: (res2) => {
  133. success?.({
  134. tempFilePath: res2.tempFilePath,
  135. isEmpty: signature?.isEmpty ?? false
  136. } as LSignatureToFileSuccess)
  137. }
  138. })
  139. }, 300)
  140. } else {
  141. success?.({
  142. tempFilePath: res.tempFilePath,
  143. isEmpty: signature?.isEmpty ?? false
  144. } as LSignatureToFileSuccess)
  145. }
  146. },
  147. fail: (res) => {
  148. fail?.(res)
  149. },
  150. complete: (res) => {
  151. complete?.(res)
  152. }
  153. } as TakeSnapshotOptions)
  154. // #endif
  155. // #ifdef WEB
  156. // @ts-ignore
  157. const { backgroundColor, backgroundImage, landscape, boundingBox } = props
  158. const { quality = 1 } = options
  159. const flag = landscape || backgroundColor || boundingBox
  160. const type = `image/${format}`.replace(/jpg/, 'jpeg');
  161. const image = canvas?.toDataURL(!flag && type, !flag && quality)
  162. if (flag) {
  163. // @ts-ignore
  164. const canvas = document.createElement('canvas')
  165. // @ts-ignore
  166. const pixelRatio = signature?.canvas.get('pixelRatio')
  167. // @ts-ignore
  168. let width = signature?.canvas.get('width')
  169. // @ts-ignore
  170. let height = signature?.canvas.get('height')
  171. let x = 0
  172. let y = 0
  173. // @ts-ignore
  174. const next = () => {
  175. const size = [width, height]
  176. if (landscape) {
  177. size.reverse()
  178. }
  179. canvas.width = size[0] * pixelRatio
  180. canvas.height = size[1] * pixelRatio
  181. const param = [x, y, width, height, 0, 0, width, height].map(item => item * pixelRatio)
  182. const context = canvas.getContext('2d')
  183. if (landscape) {
  184. context.translate(0, width * pixelRatio)
  185. context.rotate(-Math.PI / 2)
  186. }
  187. if (backgroundColor) {
  188. context.fillStyle = backgroundColor
  189. context.fillRect(0, 0, width * pixelRatio, height * pixelRatio)
  190. }
  191. const drawImage = () => {
  192. // @ts-ignore
  193. context.drawImage(signature?.canvas!.get('el'), ...param)
  194. success?.({
  195. tempFilePath: canvas.toDataURL(type, quality),
  196. // @ts-ignore
  197. isEmpty: signature?.isEmpty() ?? false
  198. } as LSignatureToFileSuccess)
  199. canvas.remove()
  200. }
  201. if (backgroundImage) {
  202. const img = new Image();
  203. img.onload = () => {
  204. context.drawImage(img, ...param)
  205. drawImage()
  206. }
  207. img.src = backgroundImage
  208. }
  209. if (!backgroundImage) {
  210. drawImage()
  211. }
  212. }
  213. if (boundingBox) {
  214. // @ts-ignore
  215. const res = signature?.getContentBoundingBox()
  216. width = res.width
  217. height = res.height
  218. x = res.startX
  219. y = res.startY
  220. next()
  221. } else {
  222. next()
  223. }
  224. } else {
  225. success?.({
  226. tempFilePath: image,
  227. // @ts-ignore
  228. isEmpty: signature?.isEmpty() ?? false
  229. } as LSignatureToFileSuccess)
  230. }
  231. // #endif
  232. }
  233. defineExpose({
  234. clear,
  235. redo,
  236. undo,
  237. canvasToTempFilePath,
  238. })
  239. onMounted(() => {
  240. nextTick(() => {
  241. const width = signatureRef.value?.offsetWidth
  242. const height = signatureRef.value?.offsetHeight
  243. // #ifdef APP
  244. landscapeStyle.value.set('width', `${height}px`)
  245. landscapeStyle.value.set('height', `${width}px`)
  246. landscapeImageStyle.value.set('width', `${width}px`)
  247. landscapeImageStyle.value.set('height', `${height}px`)
  248. landscapeImageStyle.value.set('transform', `rotate(-90deg) translateY(${width}px)`)
  249. signature = new Signature(signatureRef.value!)
  250. // #endif
  251. // #ifdef WEB
  252. canvas = document.createElement('canvas')
  253. canvas!.style = 'width: 100%; height: 100%;'
  254. signatureRef.value?.appendChild(canvas as UniElement)
  255. // @ts-ignore
  256. signature = new Signature({ el: canvas })
  257. let isTouch = false
  258. touchstart = (event: UniMouseEvent) => {
  259. isTouch = true
  260. const rect = canvas?.getBoundingClientRect()
  261. // @ts-ignore
  262. signature!.canvas.emit('touchstart', {
  263. points: [
  264. {
  265. x: event.clientX - rect.left,
  266. y: event.clientY - rect.top
  267. }
  268. ]
  269. })
  270. }
  271. touchmove = (event: UniMouseEvent) => {
  272. if(!isTouch) return
  273. const rect = canvas?.getBoundingClientRect()
  274. // @ts-ignore
  275. signature!.canvas.emit('touchmove', {
  276. points: [
  277. {
  278. x: event.clientX - rect.left,
  279. y: event.clientY - rect.top
  280. }
  281. ]
  282. })
  283. }
  284. touchend = (event: UniMouseEvent) => {
  285. isTouch = false
  286. const rect = canvas?.getBoundingClientRect();
  287. // @ts-ignore
  288. signature!.canvas.emit('touchend', {
  289. points: [
  290. {
  291. x: event.clientX - rect.left,
  292. y: event.clientY - rect.top
  293. }
  294. ]
  295. })
  296. }
  297. canvas?.addEventListener('mousedown', touchstart)
  298. canvas?.addEventListener('mousemove', touchmove)
  299. canvas?.addEventListener('mouseup', touchend)
  300. canvas?.addEventListener('mouseleave', touchend)
  301. // #endif
  302. watchEffect(() => {
  303. const options : LSignatureOptions = {
  304. penColor: props.penColor,
  305. openSmooth: props.openSmooth,
  306. disableScroll: props.disableScroll,
  307. disabled: props.disabled,
  308. penSize: props.penSize,
  309. minLineWidth: props.minLineWidth,
  310. maxLineWidth: props.maxLineWidth,
  311. minSpeed: props.minSpeed,
  312. maxWidthDiffRate: props.maxWidthDiffRate,
  313. maxHistoryLength: props.maxHistoryLength
  314. }
  315. // #ifdef APP
  316. signature?.setOption(options)
  317. // #endif
  318. // #ifdef WEB
  319. // @ts-ignore
  320. signature?.pen.setOption(options)
  321. // #endif
  322. })
  323. })
  324. })
  325. onUnmounted(()=>{
  326. // #ifdef WEB
  327. canvas?.removeEventListener('mousedown', touchstart)
  328. canvas?.removeEventListener('mousemove', touchmove)
  329. canvas?.removeEventListener('mouseup', touchend)
  330. canvas?.removeEventListener('mouseleave', touchend)
  331. canvas?.remove()
  332. // #endif
  333. })
  334. </script>
  335. <style lang="scss">
  336. .l-signature {
  337. flex: 1;
  338. &-landscape {
  339. position: absolute;
  340. pointer-events: none;
  341. left: 1000rpx;
  342. }
  343. &-image {
  344. transform-origin: 0% 0%;
  345. }
  346. }
  347. </style>