annual-energy.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. <template>
  2. <view class="annual-energy-page">
  3. <view v-if="loading" class="loading-state"><text>加载中...</text></view>
  4. <view v-else-if="error" class="error-state"><text class="error-text">{{ error }}</text></view>
  5. <view v-else-if="energy" class="content">
  6. <view class="year-selector">
  7. <text class="year-current">{{ energy.year || currentYear }}年</text>
  8. <text class="year-label">流年能量</text>
  9. </view>
  10. <view class="info-card">
  11. <view class="card-title-bar"><text class="card-title">五维能量分布</text></view>
  12. <view class="radar-container">
  13. <canvas canvas-id="energyRadar" id="energyRadar" class="radar-canvas"></canvas>
  14. </view>
  15. </view>
  16. <view class="info-card" v-if="energy.jupiterPhase || energy.saturnPhase">
  17. <view class="card-title-bar"><text class="card-title">流年星象</text></view>
  18. <view class="stars-grid">
  19. <view v-if="energy.jupiterPhase" class="star-item">
  20. <text class="star-name">木星</text>
  21. <text class="star-phase">{{ energy.jupiterPhase }}</text>
  22. </view>
  23. <view v-if="energy.saturnPhase" class="star-item">
  24. <text class="star-name">土星</text>
  25. <text class="star-phase">{{ energy.saturnPhase }}</text>
  26. </view>
  27. </view>
  28. </view>
  29. <view class="info-card">
  30. <view class="card-title-bar"><text class="card-title">各维度分析</text></view>
  31. <view class="dimensions-list">
  32. <view v-for="(value, key) in energy.dimensions" :key="key" class="dimension-item">
  33. <view class="dim-header">
  34. <text class="dim-label">{{ dimLabel(key) }}</text>
  35. <text class="dim-value">{{ value }}分</text>
  36. </view>
  37. <view class="bar-track"><view class="bar-fill" :style="{width: value + '%', background: dimColor(key)}"></view></view>
  38. </view>
  39. </view>
  40. </view>
  41. <view class="info-card" v-if="energy.highlights && energy.highlights.length > 0">
  42. <view class="card-title-bar"><text class="card-title">本年重点</text></view>
  43. <view class="highlight-list">
  44. <view v-for="(h, idx) in energy.highlights" :key="idx" class="highlight-item">
  45. <text class="highlight-text">{{ h }}</text>
  46. </view>
  47. </view>
  48. </view>
  49. </view>
  50. </view>
  51. </template>
  52. <script>
  53. export default {
  54. data: function() {
  55. return { energy: null, loading: false, error: null, memberId: null, currentYear: new Date().getFullYear() }
  56. },
  57. onLoad: function(query) { this.memberId = query.memberId; this.loadEnergy() },
  58. onShow: function() {
  59. var self = this
  60. if (self.energy) { self.$nextTick(function() { self.drawRadar() }) }
  61. },
  62. methods: {
  63. loadEnergy: function() {
  64. var self = this
  65. if (!self.memberId) { self.error = '参数错误'; return }
  66. self.loading = true; self.error = null
  67. self.$store.dispatch('tianpan/loadAnnualEnergy', { memberId: parseInt(self.memberId), year: self.currentYear })
  68. .then(function(data) { self.energy = data })
  69. .catch(function(e) { self.error = e.message || '加载失败' })
  70. .finally(function() { self.loading = false; self.$nextTick(function() { self.drawRadar() }) })
  71. },
  72. goBack: function() { uni.navigateBack() },
  73. drawRadar: function() {
  74. var self = this
  75. if (!self.energy || !self.energy.dimensions) return
  76. var d = self.energy.dimensions
  77. var w = uni.getWindowInfo().windowWidth
  78. var size = Math.min(w - 80, 260)
  79. var ctx = uni.createCanvasContext('energyRadar')
  80. var cx = size / 2, cy = size / 2, r = size / 2 - 30
  81. var dims = ['body', 'mind', 'wisdom', 'action', 'wealth']
  82. var n = dims.length
  83. var angleStep = Math.PI * 2 / n
  84. var values = dims.map(function(di) { return (d[di] || 0) / 100 })
  85. ctx.clearRect(0, 0, size, size)
  86. for (var i = 1; i <= 4; i++) {
  87. var rr = r * i / 4
  88. ctx.beginPath()
  89. for (var j = 0; j <= n; j++) {
  90. var a = j * angleStep - Math.PI / 2
  91. var px = cx + rr * Math.cos(a), py = cy + rr * Math.sin(a)
  92. if (j === 0) ctx.moveTo(px, py)
  93. else ctx.lineTo(px, py)
  94. }
  95. ctx.closePath(); ctx.strokeStyle = '#E5E7EB'; ctx.lineWidth = 0.5; ctx.stroke()
  96. }
  97. for (var k = 0; k < n; k++) {
  98. var a2 = k * angleStep - Math.PI / 2
  99. ctx.beginPath(); ctx.moveTo(cx, cy)
  100. ctx.lineTo(cx + r * Math.cos(a2), cy + r * Math.sin(a2))
  101. ctx.strokeStyle = '#D1D5DB'; ctx.lineWidth = 0.5; ctx.stroke()
  102. }
  103. ctx.beginPath()
  104. for (var m = 0; m < n; m++) {
  105. var a3 = m * angleStep - Math.PI / 2
  106. var vx = cx + r * values[m] * Math.cos(a3), vy = cy + r * Math.sin(a3)
  107. if (m === 0) ctx.moveTo(vx, vy)
  108. else ctx.lineTo(vx, vy)
  109. }
  110. ctx.closePath(); ctx.fillStyle = 'rgba(249, 115, 22, 0.2)'; ctx.fill()
  111. ctx.strokeStyle = '#F97316'; ctx.lineWidth = 2; ctx.stroke()
  112. ctx.draw()
  113. },
  114. dimLabel: function(key) { var map = { body: '身', mind: '心', wisdom: '智', action: '行', wealth: '富' }; return map[key] || key },
  115. dimColor: function(key) { var map = { body: '#10B981', mind: '#EC4899', wisdom: '#8B5CF6', action: '#F97316', wealth: '#3B82F6' }; return map[key] || '#F97316' }
  116. }
  117. }
  118. </script>
  119. <style scoped>
  120. .annual-energy-page { min-height: 100vh; background: #F3F4F6; }
  121. .nav-right { width: 40px; }
  122. .loading-state, .error-state { padding: 60px 20px; display: flex; justify-content: center; align-items: center; }
  123. .error-text { color: #EF4444; font-size: 14px; }
  124. .content { padding: 12px; }
  125. .year-selector { text-align: center; padding: 16px 0 8px; }
  126. .year-current { font-size: 28px; font-weight: bold; color: #F97316; }
  127. .year-label { font-size: 13px; color: #9CA3AF; display: block; }
  128. .info-card { background: #FFF; border-radius: 12px; padding: 16px; margin-bottom: 12px; }
  129. .card-title-bar { margin-bottom: 12px; border-bottom: 1px solid #F3F4F6; padding-bottom: 8px; }
  130. .card-title { font-size: 15px; font-weight: bold; color: #1F2937; }
  131. .radar-container { display: flex; justify-content: center; padding: 8px 0; }
  132. .radar-canvas { width: 260px; height: 260px; }
  133. .stars-grid { display: flex; gap: 12px; }
  134. .star-item { flex: 1; background: #FFF7ED; border-radius: 8px; padding: 12px; display: flex; flex-direction: column; align-items: center; }
  135. .star-name { font-size: 13px; color: #9CA3AF; }
  136. .star-phase { font-size: 14px; color: #F97316; font-weight: bold; margin-top: 4px; }
  137. .dimensions-list { display: flex; flex-direction: column; }
  138. .dimension-item { margin-bottom: 14px; }
  139. .dim-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
  140. .dim-label { font-size: 13px; color: #374151; font-weight: 500; }
  141. .dim-value { font-size: 13px; color: #F97316; font-weight: bold; }
  142. .bar-track { height: 6px; background: #E5E7EB; border-radius: 3px; margin-bottom: 4px; }
  143. .bar-fill { height: 100%; border-radius: 3px; }
  144. .highlight-list { display: flex; flex-direction: column; }
  145. .highlight-item { padding: 6px 0; border-bottom: 1px solid #F3F4F6; }
  146. .highlight-item:last-child { border-bottom: none; }
  147. .highlight-text { font-size: 13px; color: #374151; line-height: 1.5; }
  148. </style>