1. import styles from './index.less'
    2. import { PureComponent, createRef } from 'react'
    3. import QRCode from 'qrcode'
    4. import html2canvas from 'html2canvas'
    5. import classnames from 'classnames'
    6. import preventScroll from 'prevent-scroll'
    7. import growingService from '@xb/services/growing'
    8. import MaskBasic from '@xb/components/Mask/Basic'
    9. import MaskCenter from '@xb/components/Mask/Center'
    10. import ButtonBasic from '@xb/components/Button/Basic'
    11. import Line from '@xb/components/Line/Basic'
    12. import { MountPoint, sleep } from '@xb/utils/dom'
    13. import { device } from '@xb/utils/userAgent'
    14. import creditService from '@xb/services/credit'
    15. import iconLogoComplete from '@xb/assets/icons/icon-logo-complete.png'
    16. import ellipseBg from '@xb/assets/ellipse-bg.png'
    17. import userImg from '@xb/assets/user.png'
    18. class ModalComponent extends PureComponent {
    19. constructor(props) {
    20. super(props)
    21. const { onClick, uniqueId, noteId } = this.props
    22. this.onClick = onClick
    23. this.node = createRef()
    24. this.container = createRef()
    25. this.saveImage = createRef()
    26. this.avatar = createRef()
    27. this.startTime = ''
    28. this.endTime = ''
    29. const link = `${window.location.origin}/course/note/landing/${uniqueId}/${noteId}?sourceType=h5wxshare`
    30. this.action.generateQR(link)
    31. this.state = {
    32. once: true,
    33. qrcode: false,
    34. }
    35. }
    36. action = {
    37. generateQR: async url => {
    38. try {
    39. const baseImage = await QRCode.toDataURL(url)
    40. const image = new Image()
    41. image.src = baseImage
    42. this.node.current.appendChild(image)
    43. this.setState({ qrcode: true })
    44. } catch (err) {
    45. console.error(err)
    46. }
    47. },
    48. generateImage: async () => {
    49. const canvas = this.action.drawCanvas(this.container.current)
    50. await sleep(300)
    51. html2canvas(this.container.current, {
    52. useCORS: true,
    53. logging: false,
    54. canvas,
    55. scale: 10,
    56. letterRendering: true,
    57. }).then(canvas => {
    58. const image = new Image()
    59. image.src = canvas.toDataURL('image/png')
    60. this.saveImage.current.appendChild(image)
    61. })
    62. },
    63. checkPicture: async url => {
    64. return new Promise(resolve => {
    65. const image = new Image()
    66. image.src = url
    67. image.onload = () => {
    68. resolve(url)
    69. }
    70. image.onerror = () => {
    71. resolve(userImg)
    72. }
    73. })
    74. },
    75. DPR: () => {
    76. if (window.devicePixelRatio && window.devicePixelRatio > 1) {
    77. return window.devicePixelRatio
    78. }
    79. return 1
    80. },
    81. parseValue: value => {
    82. return parseInt(value, 10)
    83. },
    84. drawCanvas: node => {
    85. const box = window.getComputedStyle(node)
    86. // DOM 节点计算后宽高
    87. const width = this.action.parseValue(box.width)
    88. const height = this.action.parseValue(box.height)
    89. // 获取像素比
    90. const scaleBy = this.action.DPR()
    91. // 创建自定义 canvas 元素
    92. const canvas = document.createElement('canvas')
    93. // 设定 canvas 元素属性宽高为 DOM 节点宽高 * 像素比
    94. canvas.width = width * scaleBy
    95. canvas.height = height * scaleBy
    96. // 设定 canvas css宽高为 DOM 节点宽高
    97. canvas.style.width = `${width}px`
    98. canvas.style.height = `${height}px`
    99. // 获取画笔
    100. const context = canvas.getContext('2d')
    101. // 【重要】关闭抗锯齿
    102. context.mozImageSmoothingEnabled = false
    103. context.msImageSmoothingEnabled = false
    104. context.imageSmoothingEnabled = false
    105. // 将所有绘制内容放大像素比倍
    106. context.scale(scaleBy, scaleBy)
    107. return canvas
    108. },
    109. touchstart: () => {
    110. this.startTime = +new Date()
    111. },
    112. touchend: () => {
    113. this.endTime = +new Date()
    114. const { uniqueId } = this.props
    115. if (this.endTime - this.startTime > 700) {
    116. const { payload } = this.props
    117. growingService.noteShareImage(payload)
    118. try {
    119. creditService.shareNote(uniqueId)
    120. } catch (error) {
    121. console.error(error)
    122. }
    123. }
    124. },
    125. }
    126. render() {
    127. const { userUrl, userName, createdTime, content, productTitle } = this.props
    128. const { once, qrcode } = this.state
    129. const isIOSBox = device.isIOS ? styles.isIOSBox : ''
    130. const isIOSNote = device.isIOS ? styles.isIOSNote : ''
    131. const avatar = this.action.checkPicture(userUrl)
    132. avatar.then(url => {
    133. this.avatar.current.src = url
    134. if (once && qrcode) {
    135. this.action.generateImage()
    136. this.setState({ once: false })
    137. }
    138. })
    139. return (
    140. <MaskBasic onClick={this.onClick}>
    141. <MaskCenter>
    142. <div className={classnames(styles.box, isIOSBox)} onClick={e => e.stopPropagation()}>
    143. <div
    144. ref={this.saveImage}
    145. className={styles.containerImage}
    146. onTouchStart={this.action.touchstart}
    147. onTouchEnd={this.action.touchend}
    148. />
    149. <div ref={this.container} className={styles.container}>
    150. <img className={styles.logo} src={iconLogoComplete} />
    151. <img className={styles.ellipse} src={ellipseBg} />
    152. <div className={styles.content}>
    153. <div className={styles.product}>
    154. <span>来自课程</span>
    155. <p>{productTitle}</p>
    156. </div>
    157. <div className={styles.userInfo}>
    158. <img ref={this.avatar} className={styles.avatar} crossOrigin='anonymous' />
    159. <div className={styles.user}>
    160. <h3>{userName}</h3>
    161. <p>{createdTime}</p>
    162. </div>
    163. </div>
    164. <p className={classnames(styles.note, isIOSNote)}>{content}</p>
    165. <Line className={styles.line} size={15} />
    166. <div className={styles.qrcodeInfo}>
    167. <div className={styles.qrcode} ref={this.node} />
    168. <div className={styles.description}>
    169. <h3>长按识别二维码</h3>
    170. <p>查看完整笔记,亦可点赞评论</p>
    171. </div>
    172. </div>
    173. </div>
    174. </div>
    175. <div className={styles.button}>
    176. <ButtonBasic style={{ width: '100%', color: '#222222', fontWeight: 400, boxShadow: 'none' }}>长按按钮分享笔记</ButtonBasic>
    177. </div>
    178. </div>
    179. <div className={styles.close} />
    180. </MaskCenter>
    181. </MaskBasic>
    182. )
    183. }
    184. }
    185. class GlobalModalSharePicture extends MountPoint {
    186. hide = () => {
    187. preventScroll.off()
    188. this.removeMountPoint()
    189. }
    190. show = options => {
    191. preventScroll.on()
    192. this.render(<ModalComponent onClick={this.hide} {...options} />)
    193. }
    194. }
    195. export default new GlobalModalSharePicture()