index.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. <template>
  2. <canvas ref="shareCanvas" :width="canvasWidth" :height="canvasHeight"></canvas>
  3. <Loading :visible="loading" />
  4. </template>
  5. <script setup>
  6. import { ref, watch } from 'vue'
  7. import { getJobAdvertisedShareQrcode } from '@/api/position'
  8. import { saveShareQuery } from '@/api/recruit/personal/jobFair'
  9. const emit = defineEmits(['success'])
  10. const props = defineProps({
  11. canvasWidth: {
  12. type: Number,
  13. default: 540
  14. },
  15. canvasHeight: {
  16. type: Number,
  17. default: 788
  18. },
  19. show: Boolean,
  20. enterpriseName: String,
  21. logoUrl: String,
  22. positionList: Array,
  23. jobFairId: [String, Number],
  24. enterpriseId: [String, Number],
  25. bgImg: String,
  26. backgroundColor: String
  27. })
  28. const loading = ref(false)
  29. const shareCanvas = ref(null)
  30. const imgSrc = ref('')
  31. const drawCanvas = () => {
  32. loading.value = true
  33. const canvas = shareCanvas.value
  34. const ctx = canvas.getContext('2d')
  35. const img = new Image()
  36. img.crossOrigin = 'anonymous'
  37. img.onload = async () => {
  38. //清空画布
  39. ctx.clearRect(0, 0, props.canvasWidth, props.canvasHeight)
  40. // 设置背景图片
  41. ctx.drawImage(img, 0, 0, props.canvasWidth, props.canvasHeight)
  42. // 分享二维码
  43. const secondImg = new Image()
  44. secondImg.crossOrigin = 'anonymous'
  45. secondImg.onload = () => {
  46. // 分享二维码
  47. ctx.drawImage(secondImg, 80, 550, 110, 110)
  48. ctx.font = 'bold 18px Arial'
  49. ctx.fillStyle = '#000'
  50. ctx.fillText('海量职位火热招聘中', 280, 605)
  51. ctx.font = '15px Arial'
  52. ctx.fillStyle = '#999'
  53. ctx.fillText('长按图片识别二维码', 290, 625)
  54. const thirdImg = new Image()
  55. thirdImg.crossOrigin = 'anonymous'
  56. thirdImg.onload = () => {
  57. // 企业头像
  58. ctx.save()
  59. const secondImgWidth = 120
  60. const secondImgHeight = 120
  61. const x = (canvas.width - secondImgWidth) / 2
  62. const y = canvas.height - secondImgHeight - 460
  63. // 设置边框样式
  64. ctx.strokeStyle = '#E1E4E9'
  65. ctx.lineWidth = 1
  66. // 绘制圆角矩形路径
  67. ctx.beginPath()
  68. const logoRadius = 4
  69. ctx.moveTo(x + logoRadius, y)
  70. ctx.lineTo(x + secondImgWidth - logoRadius, y)
  71. ctx.quadraticCurveTo(x + secondImgWidth, y, x + secondImgWidth, y + logoRadius)
  72. ctx.lineTo(x + secondImgWidth, y + secondImgHeight - logoRadius)
  73. ctx.quadraticCurveTo(x + secondImgWidth, y + secondImgHeight, x + secondImgWidth - logoRadius, y + secondImgHeight)
  74. ctx.lineTo(x + logoRadius, y + secondImgHeight)
  75. ctx.quadraticCurveTo(x, y + secondImgHeight, x, y + secondImgHeight - logoRadius)
  76. ctx.lineTo(x, y + logoRadius)
  77. ctx.quadraticCurveTo(x, y, x + logoRadius, y)
  78. ctx.closePath()
  79. ctx.clip()
  80. ctx.drawImage(thirdImg, x, y, secondImgWidth, secondImgHeight)
  81. // 绘制边框
  82. ctx.stroke()
  83. ctx.restore()
  84. // 企业名称
  85. const maxTextWidth = 400
  86. const text = props.enterpriseName
  87. const fontStyle = 'bold 24px Arial'
  88. ctx.font = fontStyle
  89. let truncatedText = text
  90. while (ctx.measureText(truncatedText + '...').width > maxTextWidth && truncatedText.length > 0) {
  91. truncatedText = truncatedText.slice(0, -1)
  92. }
  93. if (truncatedText !== text) truncatedText += '...'
  94. const textX = x + (secondImgWidth - ctx.measureText(truncatedText).width) / 2
  95. const textY = y + secondImgHeight + 30
  96. ctx.fillStyle = '#000'
  97. ctx.fillText(truncatedText, textX, textY)
  98. // 职位标签
  99. const tagPaddingLeftRight = 20
  100. const tagPaddingTopBottom = 10
  101. const tagRadius = 8
  102. const tagSpacing = 22
  103. let tagY = textY + tagSpacing + 10
  104. props.positionList.forEach((tag) => {
  105. let truncatedTag = tag
  106. ctx.font = '18px Arial' // 绘制前设置字体,否则tagWidth受前面的font fontStyle影响
  107. while (ctx.measureText(truncatedTag + '...').width > maxTextWidth - 2 * tagPaddingLeftRight && truncatedTag.length > 0) {
  108. truncatedTag = truncatedTag.slice(0, -1)
  109. }
  110. if (truncatedTag !== tag) truncatedTag += '...'
  111. const tagWidth = ctx.measureText(truncatedTag).width + 2 * tagPaddingLeftRight
  112. const tagX = x + (secondImgWidth - tagWidth) / 2
  113. ctx.fillStyle = '#00B760'
  114. ctx.beginPath()
  115. ctx.moveTo(tagX + tagRadius, tagY)
  116. ctx.lineTo(tagX + tagWidth - tagRadius, tagY)
  117. ctx.quadraticCurveTo(tagX + tagWidth, tagY, tagX + tagWidth, tagY + tagRadius)
  118. ctx.lineTo(tagX + tagWidth, tagY + tagPaddingTopBottom * 2)
  119. ctx.quadraticCurveTo(tagX + tagWidth, tagY + tagPaddingTopBottom * 2 + tagRadius, tagX + tagWidth - tagRadius, tagY + tagPaddingTopBottom * 2 + tagRadius)
  120. ctx.lineTo(tagX + tagRadius, tagY + tagPaddingTopBottom * 2 + tagRadius)
  121. ctx.quadraticCurveTo(tagX, tagY + tagPaddingTopBottom * 2 + tagRadius, tagX, tagY + tagPaddingTopBottom * 2)
  122. ctx.lineTo(tagX, tagY + tagRadius);
  123. ctx.quadraticCurveTo(tagX, tagY, tagX + tagRadius, tagY)
  124. ctx.closePath()
  125. ctx.fill()
  126. ctx.fillStyle = '#fff'
  127. ctx.fillText(truncatedTag, tagX + tagPaddingLeftRight, tagY + tagPaddingTopBottom + 10)
  128. tagY += tagPaddingTopBottom * 2 + tagSpacing
  129. })
  130. imgSrc.value = canvas.toDataURL('image/png')
  131. loading.value = false
  132. emit('success', imgSrc.value)
  133. }
  134. thirdImg.src = props.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'
  135. }
  136. // 保存分享参数
  137. const params = {
  138. jobFairId: props.jobFairId,
  139. enterpriseId: props.enterpriseId,
  140. entName: props.enterpriseName,
  141. backgroundColor: props.backgroundColor
  142. }
  143. try {
  144. const result = await saveShareQuery(params)
  145. const query = {
  146. scene: 'id=' + result,
  147. path: 'pagesB/jobFair/positionClassification',
  148. width: 200,
  149. autoColor: false,
  150. checkPath: true,
  151. hyaline: false
  152. }
  153. try {
  154. const data = await getJobAdvertisedShareQrcode(query)
  155. secondImg.src = 'data:image/png;base64,' + data
  156. } catch {
  157. loading.value = false
  158. }
  159. } catch {
  160. loading.value = false
  161. }
  162. }
  163. img.onerror = (error) => {
  164. console.error('Failed to load image:', error)
  165. loading.value = false
  166. }
  167. img.src = props.bgImg
  168. }
  169. watch(
  170. () => props.show,
  171. (val) => {
  172. if (val) drawCanvas()
  173. }
  174. )
  175. </script>