l-signature.uvue 8.9 KB

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