ChartShareCanvas.vue 6.5 KB

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