• uniapp微信小程序电子签名


    先上效果图,不满意可以直接关闭这页签


    新建成单独的组件,然后具体功能引入,具体功能点击签名按钮,把当前功能页面用样式隐藏掉,v-show和v-if也行,然后再把这个组件显示出来。


    【签名-撤销】原理是之前绘画时把全部轨迹路径都记录下来,然后点击撤销时,清空画布,路径数组去掉最后一次绘画动作,然后再把剩余路径又全部画上去,这么干后会路径会出现锯齿,我也莫得办法了,将就着用了。


    【签名-完成】点击完成会判断路径数组是否有值,如果没有则说明没有签名,有则把画布保存成图片,然后再把这个图片以指定尺寸画入另一个画布,避免保存下来的图片分辨率过大,导致文件太大。画上另一个画布之后,这个画布再保存成图片,然后图片再转成base64格式返回给主页面,要是不想转base64可以把具体代码去掉。生成的图片是垂直,应该以逆时针旋转90度保存的,奈何前端实力不过关,怎么都处理不好这个逆时针旋转90度,只能在上传到后端后,用后端旋转了(丢人).........如果有人能处理好这个,麻烦评论留下代码


    主页面引入组件并注册,然后用v-show控制是否显示,主页面样式自己调整好,让电子签名可以覆盖整个页面。


    [具体组件代码]

    1. <script>
    2. export default {
    3. data() {
    4. return {
    5. // 距离顶部高度
    6. top: void (0),
    7. isDrawing: false,
    8. startX: 0,
    9. startY: 0,
    10. strokes: [],
    11. canvasWidth: 0,
    12. canvasHeight: 0
    13. }
    14. },
    15. mounted() {
    16. // 获取手机状态栏和导航栏高度,和手机屏幕可用宽高度
    17. const _this = this
    18. uni.getSystemInfo({
    19. success: function(res) {
    20. _this.canvasWidth = res.windowWidth
    21. _this.canvasHeight = res.windowHeight
    22. const custom = uni.getMenuButtonBoundingClientRect()
    23. // 导航栏胶囊高度 + (胶囊距离顶部高度 - 状态栏高度) * 2 + 状态栏高度 + 20内边距
    24. _this.top = custom.height + (custom.top - res.statusBarHeight) * 2 + res.statusBarHeight + 4
    25. }
    26. })
    27. // 创建画布
    28. const ctx = uni.createCanvasContext('signCanvas', this)
    29. ctx.setStrokeStyle('#000')
    30. ctx.setLineWidth(4)
    31. ctx.setLineCap('round')
    32. ctx.setLineJoin('round')
    33. ctx.draw()
    34. this.canvasContext = ctx
    35. },
    36. methods: {
    37. handleTouchStart(e) {
    38. // 阻止默认滚动行为
    39. e.preventDefault()
    40. const touch = e.touches[0]
    41. this.isDrawing = true
    42. this.startX = touch.x
    43. this.startY = touch.y
    44. this.strokes.push({
    45. type: 'start',
    46. x: touch.x,
    47. y: touch.y
    48. })
    49. },
    50. handleTouchMove(e) {
    51. e.preventDefault() // 阻止默认滚动行为
    52. if (!this.isDrawing) {
    53. return
    54. }
    55. const touch = e.touches[0]
    56. this.canvasContext.moveTo(this.startX, this.startY)
    57. this.canvasContext.lineTo(touch.x, touch.y)
    58. this.canvasContext.stroke()
    59. this.canvasContext.draw(true)
    60. this.startX = touch.x
    61. this.startY = touch.y
    62. this.strokes.push({
    63. type: 'move',
    64. x: touch.x,
    65. y: touch.y
    66. })
    67. },
    68. handleTouchEnd(e) {
    69. e.preventDefault() // 阻止默认滚动行为
    70. this.isDrawing = false
    71. },
    72. // 撤销
    73. onUndo () {
    74. // 先清空当前画布
    75. this.onClearRect(false)
    76. if (this.strokes.length) {
    77. // 去掉最后一次绘画的路径
    78. while (this.strokes.pop().type !== 'start'){}
    79. // 剩余路径全部绘制到画布上
    80. for (let i = 0; i < this.strokes.length; i++) {
    81. const item = this.strokes[i]
    82. if(item.type === 'start') {
    83. // 绘制起始点
    84. this.canvasContext.beginPath()
    85. this.canvasContext.moveTo(item.x, item.y)
    86. } else if(item.type === 'move') {
    87. // 绘制线条
    88. this.canvasContext.lineTo(item.x, item.y)
    89. this.canvasContext.stroke()
    90. }
    91. }
    92. this.canvasContext.draw(true)
    93. }
    94. },
    95. // 清空
    96. onClearRect (clearLine) {
    97. this.canvasContext.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
    98. this.canvasContext.draw(true)
    99. clearLine && (this.strokes = [])
    100. },
    101. // 取消
    102. onCancel () {
    103. this.onClearRect(true)
    104. this.$emit('cancel')
    105. },
    106. // 保存签名 signFlag 是否已签名
    107. onSaveSign() {
    108. if (!this.strokes.length) {
    109. // 未签名
    110. this.$emit('ok', { signFlag: false })
    111. return
    112. }
    113. // 签名保存为图片
    114. uni.canvasToTempFilePath({
    115. canvasId: 'signCanvas',
    116. quality: 0.1,
    117. success: (res) => {
    118. const tempPath = res.tempFilePath
    119. // 然后写入另一个画布
    120. const signCanvas = uni.createCanvasContext('signCanvasReduce', this)
    121. signCanvas.translate(0, 0) // 修改原点坐标(这里是已左上角为坐标原点)
    122. signCanvas.drawImage(tempPath, 0, 0, 80, 160)
    123. signCanvas.draw(false, () => {
    124. setTimeout(() => {
    125. // 另一个画布再保存为图片,目的就是缩小图片的尺寸
    126. uni.canvasToTempFilePath({
    127. canvasId: 'signCanvasReduce',
    128. quality: 0.2,
    129. success: (res) => {
    130. // 清空画布
    131. this.onClearRect(true)
    132. // 转成base64
    133. this.imagePathToBase64(res.tempFilePath).then(base64 => {
    134. this.$emit('ok', { base64, signFlag: true })
    135. })
    136. },
    137. fail: () => {
    138. // toast('生成签名图片失败')
    139. }
    140. }, this)
    141. }, 200)
    142. })
    143. },
    144. fail: (res) => {
    145. // toast('生成签名失败')
    146. }
    147. }, this)
    148. },
    149. // 根据上传后的图片转成Base64格式
    150. imagePathToBase64(url) {
    151. if (!url) {
    152. return url
    153. }
    154. return new Promise((resolve, reject) => {
    155. uni.getFileSystemManager().readFile({
    156. filePath: url,
    157. encoding: 'base64',
    158. success: fileRes => {
    159. const base64 = 'data:image/png;base64,' + fileRes.data
    160. resolve(base64)
    161. },
    162. fail: err => {
    163. // toast('生成签名失败')
    164. resolve()
    165. }
    166. })
    167. })
    168. }
    169. }
    170. }
    171. script>
    172. <style lang="scss" scoped>
    173. .panel {
    174. width: 100%;
    175. position: absolute;
    176. left: 0;
    177. bottom: 0;
    178. right: 0;
    179. top: 0;
    180. overflow-y: hidden;
    181. background-color: #FFF;
    182. }
    183. .sign-canvas {
    184. width: 100%;
    185. height: 85%;
    186. }
    187. .sign-canvas-reduce {
    188. width: 80px;
    189. height: 160px;
    190. position: absolute;
    191. top: -10000rpx;
    192. }
    193. .panel-bottom {
    194. height: 15%;
    195. display: flex;
    196. justify-content: center;
    197. padding-top: 50rpx;
    198. }
    199. .panel-bottom-btn {
    200. transform: rotate(90deg);
    201. height: 40rpx;
    202. padding: 14rpx 36rpx;
    203. font-size: 30rpx;
    204. border-radius: 20rpx;
    205. color: #FFF;
    206. background: linear-gradient(90deg, rgba(250, 197, 22, 1), rgba(255, 141, 26, 1));
    207. }
    208. .btn-gray {
    209. background: #d4d4d4;
    210. }
    211. style>
    212. <style>
    213. page {
    214. overflow-y: hidden;
    215. }
    216. style>

    继续加班了.....

    码字不易,于你有利,勿忘点赞 

     

  • 相关阅读:
    Java入门篇 之 内部类
    fplan-布局
    Android C++系列:Linux文件IO操作(一)
    【JavaWeb从零到一】会话技术Cookie&Session&JSP
    基于C语言的图论汇编
    Stream流使用——(未完)
    IP的基础知识、子网掩码、网关、CIDR
    DANAHER S21260-SRS伺服驱动器模块
    WebSocket Day03 : SpringMVC整合WebSocket
    链表增删操作问题及解决方法
  • 原文地址:https://blog.csdn.net/new_public/article/details/140107762