index.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import { getSystemInfoSync } from '../helpers/hooks/useNativeAPI'
  2. import { toDataURL, getCanvasRef } from '../helpers/hooks/useCanvasAPI'
  3. /**
  4. * 获取范围内的随机数
  5. * @param {Number} min 最小值
  6. * @param {Number} max 最大值
  7. */
  8. const randomNum = (min, max) => {
  9. return Math.floor(Math.random() * (max - min) + min)
  10. }
  11. /**
  12. * 获取范围内的随机颜色值
  13. * @param {Number} min 最小值
  14. * @param {Number} max 最大值
  15. */
  16. const randomColor = (min, max) => {
  17. const _r = randomNum(min, max)
  18. const _g = randomNum(min, max)
  19. const _b = randomNum(min, max)
  20. return `rgb(${_r}, ${_g}, ${_b})`
  21. }
  22. /**
  23. * 创建 canvas 绘图上下文
  24. * @param {Object} ctx canvas 绘图上下文
  25. * @param {Object} props 配置项
  26. * @param {String} props.str 验证码范围
  27. * @param {Number} props.num 验证码长度,默认值 6
  28. * @param {Number} props.width 画布宽度,默认值 120
  29. * @param {Number} props.height 画布高度,默认值 40
  30. * @param {String} props.bgColor 画布背景色
  31. * @param {String} props.fontColor 画布字体颜色
  32. * @param {Boolean} props.hasPoint 是否显示干扰点,默认 true
  33. * @param {Boolean} props.hasLine 是否显示干扰线,默认 true
  34. */
  35. const render = (ctx, props = {}) => {
  36. const { str, num, width, height, bgColor, fontColor, hasPoint, hasLine } = props
  37. const ratio = getSystemInfoSync(['window']).pixelRatio
  38. let vcode = ''
  39. // 绘制矩形,并设置填充色
  40. ctx.textBaseline = 'bottom'
  41. ctx.fillStyle = bgColor ? bgColor : randomColor(180, 240)
  42. ctx.scale(ratio, ratio)
  43. ctx.fillRect(0, 0, width, height)
  44. // 绘制随机生成 n 位的验证码
  45. for (let i = 0; i < num; i++) {
  46. const x = (width - 10) / num * i + 10
  47. const y = randomNum(height / 2, height)
  48. const deg = randomNum(-45, 45)
  49. const txt = str[randomNum(0, str.length)]
  50. const fontSize = randomNum(16, 40)
  51. const halfHeight = parseInt(height / 2)
  52. vcode += txt
  53. ctx.fillStyle = fontColor ? fontColor : randomColor(10, 100)
  54. ctx.font = `normal normal normal ${fontSize > halfHeight ? halfHeight : fontSize}px sans-serif`
  55. ctx.translate(x, y)
  56. ctx.rotate(deg * Math.PI / 180)
  57. ctx.fillText(txt, 0, 0)
  58. ctx.rotate(-deg * Math.PI / 180)
  59. ctx.translate(-x, -y)
  60. }
  61. // 绘制干扰线
  62. if (!!hasLine) {
  63. for (let i = 0; i < num; i++) {
  64. ctx.strokeStyle = randomColor(90, 180)
  65. ctx.beginPath()
  66. ctx.moveTo(randomNum(0, width), randomNum(0, height))
  67. ctx.lineTo(randomNum(0, width), randomNum(0, height))
  68. ctx.stroke()
  69. }
  70. }
  71. // 绘制干扰点
  72. if (!!hasPoint) {
  73. for (let i = 0; i < num * 10; i++) {
  74. ctx.fillStyle = randomColor(0, 255)
  75. ctx.beginPath()
  76. ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI)
  77. ctx.fill()
  78. }
  79. }
  80. return vcode
  81. }
  82. Component({
  83. properties: {
  84. str: {
  85. type: String,
  86. value: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
  87. },
  88. num: {
  89. type: Number,
  90. value: 6,
  91. },
  92. width: {
  93. type: Number,
  94. value: 120,
  95. },
  96. height: {
  97. type: Number,
  98. value: 40,
  99. },
  100. bgColor: {
  101. type: String,
  102. value: '',
  103. },
  104. fontColor: {
  105. type: String,
  106. value: '',
  107. },
  108. hasPoint: {
  109. type: Boolean,
  110. value: true,
  111. },
  112. hasLine: {
  113. type: Boolean,
  114. value: true,
  115. },
  116. canvasId: {
  117. type: String,
  118. value: 'wux-vcode',
  119. },
  120. },
  121. methods: {
  122. /**
  123. * 将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中
  124. */
  125. createCanvasContext(props) {
  126. const { width, height, canvasId } = props
  127. const setBase64Url = ({ value, base64Url }) => {
  128. this.triggerEvent('change', { value, base64Url })
  129. }
  130. const renderCanvas = () => getCanvasRef(canvasId, this).then((canvas) => {
  131. const ctx = canvas.getContext('2d')
  132. const ratio = getSystemInfoSync(['window']).pixelRatio
  133. const canvasWidth = width * ratio
  134. const canvasHeight = height * ratio
  135. canvas.width = canvasWidth
  136. canvas.height = canvasHeight
  137. const value = render(ctx, props)
  138. return toDataURL({ width, height }, canvas)
  139. .then((base64Url) => {
  140. ctx.restore()
  141. return { value, base64Url }
  142. })
  143. })
  144. let promise = Promise.resolve()
  145. promise = promise.then(() => {
  146. return renderCanvas()
  147. })
  148. promise = promise.then(({ value, base64Url }) => {
  149. setBase64Url({ value, base64Url })
  150. }, (err) => {
  151. this.triggerEvent('error', err)
  152. // console.error(err)
  153. })
  154. return promise
  155. },
  156. draw() {
  157. this.createCanvasContext(this.data)
  158. },
  159. },
  160. ready() {
  161. this.createCanvasContext(this.data)
  162. },
  163. })