index.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. import baseComponent from '../helpers/baseComponent'
  2. import classNames from '../helpers/libs/classNames'
  3. import styleToCssString from '../helpers/libs/styleToCssString'
  4. import { getSystemInfoSync } from '../helpers/hooks/useNativeAPI'
  5. import { toDataURL, getCanvasRef } from '../helpers/hooks/useCanvasAPI'
  6. import qrjs from './qr.js/index'
  7. /**
  8. * 字符串转换成 UTF-8
  9. * @param {String} str 文本内容
  10. */
  11. const utf16to8 = (str) => {
  12. const len = str.length
  13. let out = ''
  14. for (let i = 0; i < len; i++) {
  15. const c = str.charCodeAt(i)
  16. if ((c >= 0x0001) && (c <= 0x007F)) {
  17. out += str.charAt(i)
  18. } else if (c > 0x07FF) {
  19. out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F))
  20. out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F))
  21. out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F))
  22. } else {
  23. out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F))
  24. out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F))
  25. }
  26. }
  27. return out
  28. }
  29. baseComponent({
  30. useExport: true,
  31. properties: {
  32. prefixCls: {
  33. type: String,
  34. value: 'wux-qrcode',
  35. },
  36. typeNumber: {
  37. type: Number,
  38. value: -1,
  39. },
  40. errorCorrectLevel: {
  41. type: Number,
  42. value: 2,
  43. },
  44. width: {
  45. type: Number,
  46. value: 200,
  47. },
  48. height: {
  49. type: Number,
  50. value: 200,
  51. },
  52. whiteSpace: {
  53. type: Number,
  54. value: 0,
  55. },
  56. fgColor: {
  57. type: String,
  58. value: 'black',
  59. },
  60. bgColor: {
  61. type: String,
  62. value: 'white',
  63. },
  64. data: {
  65. type: String,
  66. value: '',
  67. },
  68. showMenuByLongpress: {
  69. type: Boolean,
  70. value: false,
  71. },
  72. qrcodeStatus: {
  73. type: String,
  74. value: 'activated',
  75. },
  76. qrcodeExpiredText: {
  77. type: String,
  78. value: '二维码过期',
  79. },
  80. qrcodeRefreshText: {
  81. type: String,
  82. value: '点击刷新',
  83. },
  84. },
  85. data: {
  86. wrapStyle: '',
  87. base64Url: '',
  88. },
  89. observers: {
  90. ['height, width'](height, width) {
  91. this.updateStyle(height, width)
  92. },
  93. ['prefixCls, typeNumber, errorCorrectLevel, width, height, whiteSpace, fgColor, bgColor, data'](...args) {
  94. this.setBase64Url(...args)
  95. },
  96. },
  97. computed: {
  98. classes: ['prefixCls', function(prefixCls) {
  99. const wrap = classNames(prefixCls)
  100. const canvas = `${prefixCls}__canvas`
  101. const image = `${prefixCls}__image`
  102. const mask = `${prefixCls}__mask`
  103. const expired = `${prefixCls}__expired`
  104. const refresh = `${prefixCls}__refresh`
  105. const icon = `${prefixCls}__icon`
  106. return {
  107. wrap,
  108. canvas,
  109. image,
  110. mask,
  111. expired,
  112. refresh,
  113. icon,
  114. }
  115. }],
  116. },
  117. methods: {
  118. updateStyle(height, width) {
  119. const wrapStyle = styleToCssString({
  120. height: `${height}px`,
  121. width: `${width}px`,
  122. })
  123. this.setData({
  124. wrapStyle,
  125. })
  126. },
  127. setBase64Url(...args) {
  128. const [
  129. prefixCls,
  130. typeNumber,
  131. errorCorrectLevel,
  132. width,
  133. height,
  134. whiteSpace,
  135. fgColor,
  136. bgColor,
  137. data,
  138. ] = args
  139. this.createCanvasContext({
  140. prefixCls,
  141. typeNumber,
  142. errorCorrectLevel,
  143. width,
  144. height,
  145. whiteSpace,
  146. fgColor,
  147. bgColor,
  148. data,
  149. })
  150. },
  151. /**
  152. * 将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中
  153. */
  154. createCanvasContext(props) {
  155. const {
  156. prefixCls,
  157. typeNumber,
  158. errorCorrectLevel,
  159. width,
  160. height,
  161. whiteSpace,
  162. fgColor,
  163. bgColor,
  164. data,
  165. } = props
  166. const qrcode = qrjs(utf16to8(data), {
  167. typeNumber,
  168. errorCorrectLevel,
  169. })
  170. const cells = qrcode.modules
  171. const tileW = (width - whiteSpace * 2) / cells.length
  172. const tileH = (height - whiteSpace * 2) / cells.length
  173. const setBase64Url = (base64Url) => {
  174. if (props.base64Url !== base64Url) {
  175. this.setData({
  176. base64Url,
  177. })
  178. this.triggerEvent('load', { base64Url })
  179. }
  180. }
  181. const canvasId = `${prefixCls}__canvas`
  182. const renderCanvas = () => getCanvasRef(canvasId, this).then((canvas) => {
  183. // always cache node
  184. this.canvas = canvas
  185. const ctx = canvas.getContext('2d')
  186. const ratio = getSystemInfoSync(['window']).pixelRatio
  187. const canvasWidth = width * ratio
  188. const canvasHeight = height * ratio
  189. canvas.width = canvasWidth
  190. canvas.height = canvasHeight
  191. ctx.scale(ratio, ratio)
  192. ctx.fillStyle = '#ffffff'
  193. ctx.fillRect(0, 0, width, height)
  194. cells.forEach((row, rdx) => {
  195. row.forEach((cell, cdx) => {
  196. ctx.fillStyle = cell ? fgColor : bgColor
  197. const x = Math.round(cdx * tileW) + whiteSpace
  198. const y = Math.round(rdx * tileH) + whiteSpace
  199. const w = (Math.ceil((cdx + 1) * tileW) - Math.floor(cdx * tileW))
  200. const h = (Math.ceil((rdx + 1) * tileH) - Math.floor(rdx * tileH))
  201. ctx.fillRect(x, y, w, h)
  202. })
  203. })
  204. return toDataURL({ width, height }, canvas)
  205. .then((base64Url) => {
  206. ctx.restore()
  207. return base64Url
  208. })
  209. })
  210. let promise = Promise.resolve()
  211. promise = promise.then(() => {
  212. return renderCanvas()
  213. })
  214. promise = promise.then((base64Url) => {
  215. setBase64Url(base64Url)
  216. }, (err) => {
  217. this.triggerEvent('error', err)
  218. // console.error(err)
  219. })
  220. return promise
  221. },
  222. /**
  223. * 手指触摸后马上离开
  224. */
  225. onTap() {
  226. this.triggerEvent('click')
  227. },
  228. /**
  229. * 蒙层的点击事件
  230. */
  231. onMaskClick() {
  232. if (this.data.qrcodeStatus === 'expired') {
  233. this.triggerEvent('refresh')
  234. }
  235. },
  236. ['export']() {
  237. const getCanvasNode = () => {
  238. return this.canvas
  239. }
  240. const getBase64Url = () => {
  241. return this.data.base64Url
  242. }
  243. return {
  244. getCanvasNode,
  245. getBase64Url,
  246. }
  247. },
  248. },
  249. ready() {
  250. const { height, width } = this.data
  251. this.updateStyle(height, width)
  252. this.createCanvasContext(this.data)
  253. },
  254. })