annual-energy.vue 7.4 KB

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