ChartShareCanvas.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <template>
  2. <view class="chart-share-canvas">
  3. <canvas canvas-id="chartCanvas" id="chartCanvas" :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"></canvas>
  4. </view>
  5. </template>
  6. <script setup>
  7. import { ref, onMounted, watch } from 'vue'
  8. const props = defineProps({
  9. chart: { type: Object, required: true },
  10. name: { type: String, default: '' },
  11. birthday: { type: String, default: '' }
  12. })
  13. const canvasWidth = 1080
  14. const canvasHeight = 800
  15. const emit = defineEmits(['ready'])
  16. function getNumColor(num) {
  17. const colors = { 1: '#D94A4A', 2: '#4A90D9', 3: '#E8A238', 4: '#2D8B67', 5: '#D4A017', 6: '#7B6FA0', 7: '#4A6FA5', 8: '#C9A84C', 9: '#B85C3A' }
  18. return colors[num] || '#FFFFFF'
  19. }
  20. function isMasterNum(num) {
  21. return num === 11 || num === 22 || num === 33
  22. }
  23. function drawCell(ctx, x, y, num, label, isOuter) {
  24. const radius = isOuter ? 28 : 32
  25. ctx.beginPath()
  26. ctx.arc(x, y, radius, 0, Math.PI * 2)
  27. ctx.setFillStyle('#1F2937')
  28. ctx.fill()
  29. ctx.setStrokeStyle(getNumColor(num))
  30. ctx.setLineWidth(4)
  31. ctx.stroke()
  32. ctx.setFillStyle(getNumColor(num))
  33. ctx.setFontSize(isOuter ? 26 : 30)
  34. ctx.setTextAlign('center')
  35. ctx.setTextBaseline('middle')
  36. ctx.fillText(num, x, y)
  37. if (isMasterNum(num)) {
  38. ctx.setFontSize(18)
  39. ctx.fillText('★', x, y + 24)
  40. }
  41. if (label) {
  42. ctx.setFontSize(12)
  43. ctx.setFillStyle('#9CA3AF')
  44. ctx.fillText(label, x, y + radius + 16)
  45. }
  46. }
  47. function drawChart() {
  48. return new Promise((resolve, reject) => {
  49. try {
  50. const ctx = uni.createCanvasContext('chartCanvas')
  51. const w = canvasWidth
  52. const h = canvasHeight
  53. // Background gradient
  54. const grad = ctx.createLinearGradient(0, 0, 0, h)
  55. grad.addColorStop(0, '#0c0a1a')
  56. grad.addColorStop(0.6, '#1a1040')
  57. grad.addColorStop(1, '#0f0c2e')
  58. ctx.setFillStyle(grad)
  59. ctx.fillRect(0, 0, w, h)
  60. // Coordinates
  61. const cx = w / 2
  62. const topY = 120
  63. const midY = 340
  64. const bottomY = 600
  65. const O = { x: cx, y: topY }
  66. const M = { x: cx - 140, y: midY }
  67. const N = { x: cx + 140, y: midY }
  68. const I = { x: cx - 260, y: bottomY - 20 }
  69. const J = { x: cx - 85, y: bottomY - 20 }
  70. const K = { x: cx + 85, y: bottomY - 20 }
  71. const L = { x: cx + 260, y: bottomY - 20 }
  72. // Outer positions
  73. const P = { x: I.x - 80, y: I.y + 100 }
  74. const Q = { x: J.x - 40, y: J.y + 120 }
  75. const R = { x: (P.x + Q.x) / 2, y: P.y + 80 }
  76. const S = { x: K.x + 40, y: K.y + 120 }
  77. const T = { x: L.x + 80, y: L.y + 100 }
  78. const U = { x: (S.x + T.x) / 2, y: S.y + 80 }
  79. const V = { x: M.x, y: M.y - 100 }
  80. const W = { x: N.x, y: N.y - 100 }
  81. const X = { x: cx, y: O.y - 100 }
  82. // Connect outer groups
  83. ctx.setStrokeStyle('rgba(255,255,255,0.15)')
  84. ctx.setLineWidth(2)
  85. ctx.beginPath()
  86. ctx.moveTo(I.x, I.y + 25).lineTo(P.x, P.y - 25)
  87. ctx.moveTo(J.x, J.y + 25).lineTo(Q.x, Q.y - 25)
  88. ctx.moveTo(P.x, P.y + 25).lineTo(Q.x, Q.y - 20).lineTo(R.x, R.y - 25)
  89. ctx.stroke()
  90. ctx.beginPath()
  91. ctx.moveTo(K.x, K.y + 25).lineTo(S.x, S.y - 25)
  92. ctx.moveTo(L.x, L.y + 25).lineTo(T.x, T.y - 25)
  93. ctx.moveTo(S.x, S.y + 25).lineTo(T.x, T.y - 20).lineTo(U.x, U.y - 25)
  94. ctx.stroke()
  95. ctx.beginPath()
  96. ctx.moveTo(M.x, M.y - 25).lineTo(V.x, V.y + 25)
  97. ctx.moveTo(N.x, N.y - 25).lineTo(W.x, W.y + 25)
  98. ctx.moveTo(V.x, V.y - 25).lineTo(W.x, W.y - 20).lineTo(X.x, X.y - 25)
  99. ctx.stroke()
  100. // Inner triangle lines
  101. ctx.setStrokeStyle('rgba(255,255,255,0.25)')
  102. ctx.setLineWidth(3)
  103. ctx.beginPath()
  104. ctx.moveTo(M.x, M.y).lineTo(N.x, N.y).lineTo(O.x, O.y).closePath()
  105. ctx.stroke()
  106. ctx.beginPath()
  107. ctx.moveTo(I.x, I.y).lineTo(J.x, J.y).lineTo(K.x, K.y).lineTo(L.x, L.y).lineTo(I.x, I.y)
  108. ctx.stroke()
  109. ctx.beginPath()
  110. ctx.moveTo(O.x, O.y).lineTo((I.x + L.x) / 2, I.y + 20)
  111. ctx.stroke()
  112. // Draw cells inner then outer
  113. drawCell(ctx, I.x, I.y, props.chart.I, 'I', false)
  114. drawCell(ctx, J.x, J.y, props.chart.J, 'J', false)
  115. drawCell(ctx, K.x, K.y, props.chart.K, 'K', false)
  116. drawCell(ctx, L.x, L.y, props.chart.L, 'L', false)
  117. drawCell(ctx, M.x, M.y, props.chart.M, 'M', false)
  118. drawCell(ctx, N.x, N.y, props.chart.N, 'N', false)
  119. drawCell(ctx, O.x, O.y, props.chart.O, 'O', false)
  120. drawCell(ctx, P.x, P.y, props.chart.P, 'P', true)
  121. drawCell(ctx, Q.x, Q.y, props.chart.Q, 'Q', true)
  122. drawCell(ctx, R.x, R.y, props.chart.R, 'R', true)
  123. drawCell(ctx, S.x, S.y, props.chart.S, 'S', true)
  124. drawCell(ctx, T.x, T.y, props.chart.T, 'T', true)
  125. drawCell(ctx, U.x, U.y, props.chart.U, 'U', true)
  126. drawCell(ctx, V.x, V.y, props.chart.V, 'V', true)
  127. drawCell(ctx, W.x, W.y, props.chart.W, 'W', true)
  128. drawCell(ctx, X.x, X.y, props.chart.X, 'X', true)
  129. // Footer
  130. ctx.setFillStyle('#FEF3C7')
  131. ctx.setFontSize(32)
  132. ctx.setTextAlign('center')
  133. const footerY = h - 80
  134. const displayName = props.name || '数字能量命盘'
  135. ctx.fillText(displayName, cx, footerY)
  136. ctx.setFontSize(24)
  137. ctx.setFillStyle('#D1D5DB')
  138. const dateStr = props.birthday ? `出生: ${props.birthday}` : ''
  139. ctx.fillText(dateStr, cx, footerY + 40)
  140. // Watermark
  141. ctx.setFontSize(18)
  142. ctx.setFillStyle('rgba(255,255,255,0.3)')
  143. ctx.fillText('数字能量学 · AI智能命理分析', cx, h - 30)
  144. ctx.draw(false, () => {
  145. emit('ready')
  146. resolve()
  147. })
  148. } catch (e) {
  149. reject(e)
  150. }
  151. })
  152. }
  153. function getTempFilePath() {
  154. return new Promise((resolve, reject) => {
  155. uni.canvasToTempFilePath({
  156. canvasId: 'chartCanvas',
  157. width: canvasWidth,
  158. height: canvasHeight,
  159. destWidth: canvasWidth,
  160. destHeight: canvasHeight,
  161. quality: 1,
  162. success: (res) => resolve(res.tempFilePath),
  163. fail: (err) => reject(err)
  164. })
  165. })
  166. }
  167. function saveToAlbum() {
  168. return getTempFilePath().then(path => {
  169. return new Promise((resolve, reject) => {
  170. uni.saveImageToPhotosAlbum({
  171. filePath: path,
  172. success: () => resolve(path),
  173. fail: (err) => reject(err)
  174. })
  175. })
  176. })
  177. }
  178. function generate() {
  179. return drawChart().then(() => getTempFilePath())
  180. }
  181. defineExpose({
  182. generate,
  183. saveToAlbum,
  184. getTempFilePath
  185. })
  186. onMounted(() => {
  187. drawChart().catch(e => console.error('Canvas draw failed', e))
  188. })
  189. watch(() => [props.chart, props.name, props.birthday], () => {
  190. drawChart().catch(e => console.error('Canvas redraw failed', e))
  191. })
  192. </script>
  193. <style scoped>
  194. .chart-share-canvas {
  195. position: fixed;
  196. left: -9999px;
  197. width: 1080px;
  198. height: 800px;
  199. }
  200. </style>