| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- <template>
- <view class="chart-share-canvas">
- <canvas canvas-id="chartCanvas" id="chartCanvas" :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"></canvas>
- </view>
- </template>
- <script setup>
- import { ref, onMounted, watch } from 'vue'
- import { getNumColorFromTheme } from './_numColors.js'
- const props = defineProps({
- chart: { type: Object, required: true },
- name: { type: String, default: '' },
- birthday: { type: String, default: '' }
- })
- const canvasWidth = 1080
- const canvasHeight = 800
- const emit = defineEmits(['ready'])
- function isMasterNum(num) {
- return num === 11 || num === 22 || num === 33
- }
- function drawCell(ctx, x, y, num, label, isOuter) {
- const radius = isOuter ? 28 : 32
- ctx.beginPath()
- ctx.arc(x, y, radius, 0, Math.PI * 2)
- ctx.setFillStyle('#1F2937')
- ctx.fill()
- ctx.setStrokeStyle(getNumColor(num))
- ctx.setLineWidth(4)
- ctx.stroke()
- ctx.setFillStyle(getNumColor(num))
- ctx.setFontSize(isOuter ? 26 : 30)
- ctx.setTextAlign('center')
- ctx.setTextBaseline('middle')
- ctx.fillText(num, x, y)
- if (isMasterNum(num)) {
- ctx.setFontSize(18)
- ctx.fillText('★', x, y + 24)
- }
- if (label) {
- ctx.setFontSize(12)
- ctx.setFillStyle('#9CA3AF')
- ctx.fillText(label, x, y + radius + 16)
- }
- }
- function drawChart() {
- return new Promise((resolve, reject) => {
- try {
- const ctx = uni.createCanvasContext('chartCanvas')
- const w = canvasWidth
- const h = canvasHeight
- // Background gradient
- const grad = ctx.createLinearGradient(0, 0, 0, h)
- grad.addColorStop(0, '#0c0a1a')
- grad.addColorStop(0.6, '#1a1040')
- grad.addColorStop(1, '#0f0c2e')
- ctx.setFillStyle(grad)
- ctx.fillRect(0, 0, w, h)
- // Coordinates
- const cx = w / 2
- const topY = 120
- const midY = 340
- const bottomY = 600
- const O = { x: cx, y: topY }
- const M = { x: cx - 140, y: midY }
- const N = { x: cx + 140, y: midY }
- const I = { x: cx - 260, y: bottomY - 20 }
- const J = { x: cx - 85, y: bottomY - 20 }
- const K = { x: cx + 85, y: bottomY - 20 }
- const L = { x: cx + 260, y: bottomY - 20 }
- // Outer positions
- const P = { x: I.x - 80, y: I.y + 100 }
- const Q = { x: J.x - 40, y: J.y + 120 }
- const R = { x: (P.x + Q.x) / 2, y: P.y + 80 }
- const S = { x: K.x + 40, y: K.y + 120 }
- const T = { x: L.x + 80, y: L.y + 100 }
- const U = { x: (S.x + T.x) / 2, y: S.y + 80 }
- const V = { x: M.x, y: M.y - 100 }
- const W = { x: N.x, y: N.y - 100 }
- const X = { x: cx, y: O.y - 100 }
- // Connect outer groups
- ctx.setStrokeStyle('rgba(255,255,255,0.15)')
- ctx.setLineWidth(2)
- ctx.beginPath()
- ctx.moveTo(I.x, I.y + 25).lineTo(P.x, P.y - 25)
- ctx.moveTo(J.x, J.y + 25).lineTo(Q.x, Q.y - 25)
- ctx.moveTo(P.x, P.y + 25).lineTo(Q.x, Q.y - 20).lineTo(R.x, R.y - 25)
- ctx.stroke()
- ctx.beginPath()
- ctx.moveTo(K.x, K.y + 25).lineTo(S.x, S.y - 25)
- ctx.moveTo(L.x, L.y + 25).lineTo(T.x, T.y - 25)
- ctx.moveTo(S.x, S.y + 25).lineTo(T.x, T.y - 20).lineTo(U.x, U.y - 25)
- ctx.stroke()
- ctx.beginPath()
- ctx.moveTo(M.x, M.y - 25).lineTo(V.x, V.y + 25)
- ctx.moveTo(N.x, N.y - 25).lineTo(W.x, W.y + 25)
- ctx.moveTo(V.x, V.y - 25).lineTo(W.x, W.y - 20).lineTo(X.x, X.y - 25)
- ctx.stroke()
- // Inner triangle lines
- ctx.setStrokeStyle('rgba(255,255,255,0.25)')
- ctx.setLineWidth(3)
- ctx.beginPath()
- ctx.moveTo(M.x, M.y).lineTo(N.x, N.y).lineTo(O.x, O.y).closePath()
- ctx.stroke()
- ctx.beginPath()
- 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)
- ctx.stroke()
- ctx.beginPath()
- ctx.moveTo(O.x, O.y).lineTo((I.x + L.x) / 2, I.y + 20)
- ctx.stroke()
- // Draw cells inner then outer
- drawCell(ctx, I.x, I.y, props.chart.I, 'I', false)
- drawCell(ctx, J.x, J.y, props.chart.J, 'J', false)
- drawCell(ctx, K.x, K.y, props.chart.K, 'K', false)
- drawCell(ctx, L.x, L.y, props.chart.L, 'L', false)
- drawCell(ctx, M.x, M.y, props.chart.M, 'M', false)
- drawCell(ctx, N.x, N.y, props.chart.N, 'N', false)
- drawCell(ctx, O.x, O.y, props.chart.O, 'O', false)
- drawCell(ctx, P.x, P.y, props.chart.P, 'P', true)
- drawCell(ctx, Q.x, Q.y, props.chart.Q, 'Q', true)
- drawCell(ctx, R.x, R.y, props.chart.R, 'R', true)
- drawCell(ctx, S.x, S.y, props.chart.S, 'S', true)
- drawCell(ctx, T.x, T.y, props.chart.T, 'T', true)
- drawCell(ctx, U.x, U.y, props.chart.U, 'U', true)
- drawCell(ctx, V.x, V.y, props.chart.V, 'V', true)
- drawCell(ctx, W.x, W.y, props.chart.W, 'W', true)
- drawCell(ctx, X.x, X.y, props.chart.X, 'X', true)
- // Footer
- ctx.setFillStyle('#FEF3C7')
- ctx.setFontSize(32)
- ctx.setTextAlign('center')
- const footerY = h - 80
- const displayName = props.name || '数字能量盘'
- ctx.fillText(displayName, cx, footerY)
- ctx.setFontSize(24)
- ctx.setFillStyle('#D1D5DB')
- const dateStr = props.birthday ? `出生: ${props.birthday}` : ''
- ctx.fillText(dateStr, cx, footerY + 40)
- // Watermark
- ctx.setFontSize(18)
- ctx.setFillStyle('rgba(255,255,255,0.3)')
- ctx.fillText('数字能量学 · AI智能命理分析', cx, h - 30)
- ctx.draw(false, () => {
- emit('ready')
- resolve()
- })
- } catch (e) {
- reject(e)
- }
- })
- }
- function getTempFilePath() {
- return new Promise((resolve, reject) => {
- uni.canvasToTempFilePath({
- canvasId: 'chartCanvas',
- width: canvasWidth,
- height: canvasHeight,
- destWidth: canvasWidth,
- destHeight: canvasHeight,
- quality: 1,
- success: (res) => resolve(res.tempFilePath),
- fail: (err) => reject(err)
- })
- })
- }
- function saveToAlbum() {
- return getTempFilePath().then(path => {
- return new Promise((resolve, reject) => {
- uni.saveImageToPhotosAlbum({
- filePath: path,
- success: () => resolve(path),
- fail: (err) => reject(err)
- })
- })
- })
- }
- function generate() {
- return drawChart().then(() => getTempFilePath())
- }
- defineExpose({
- generate,
- saveToAlbum,
- getTempFilePath
- })
- onMounted(() => {
- drawChart().catch(e => console.error('Canvas draw failed', e))
- })
- watch(() => [props.chart, props.name, props.birthday], () => {
- drawChart().catch(e => console.error('Canvas redraw failed', e))
- })
- </script>
- <style scoped>
- .chart-share-canvas {
- position: fixed;
- left: -9999px;
- width: 1080px;
- height: 800px;
- }
- </style>
|