mbti-result.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <template>
  2. <view class="mbr-page">
  3. <view class="mbr-loading" v-if="loading"><text>加载结果...</text></view>
  4. <template v-else>
  5. <view class="mbr-hero">
  6. <view class="mbr-hero-inner">
  7. <text class="mbr-type-code">{{ result.typeCode }}</text>
  8. <text class="mbr-type-name">{{ result.typeName }}</text>
  9. <text class="mbr-type-desc" v-if="result.profile && result.profile.description">{{ result.profile.description }}</text>
  10. </view>
  11. </view>
  12. <view class="mbr-section">
  13. <text class="mbr-section-title">四维度雷达</text>
  14. <view class="mbr-canvas-wrap">
  15. <canvas type="2d" id="mbtiRadar" class="mbr-canvas" @ready="onCanvasReady"></canvas>
  16. </view>
  17. </view>
  18. <view class="mbr-section" v-if="result.profile">
  19. <text class="mbr-section-title">画像详情</text>
  20. <view class="mbr-profile-list">
  21. <view class="mbr-profile-item" v-for="(val, key) in profileFields" :key="key" v-if="result.profile[key]">
  22. <text class="mbr-profile-label">{{ val.label }}</text>
  23. <text class="mbr-profile-value">{{ result.profile[key] }}</text>
  24. </view>
  25. </view>
  26. </view>
  27. <view class="mbr-section" v-if="result.profile && result.profile.energySuggestion">
  28. <text class="mbr-section-title">五维联动建议</text>
  29. <view class="mbr-energy-list">
  30. <view class="mbr-energy-item" v-for="(sug, dim) in energyMap" :key="dim">
  31. <view class="mbr-energy-head" :style="{ color: dimColors[dim] }">
  32. <view class="mbr-energy-dot" :style="{ background: dimColors[dim] }"></view>
  33. <text class="mbr-energy-label">{{ dimLabels[dim] }}</text>
  34. </view>
  35. <text class="mbr-energy-text">{{ sug }}</text>
  36. </view>
  37. </view>
  38. </view>
  39. <view class="mbr-footer">
  40. <button class="mbr-btn mbr-btn-primary" @click="goProfile">查看完整百科</button>
  41. <button class="mbr-btn mbr-btn-outline" @click="retake">重新测评</button>
  42. </view>
  43. </template>
  44. </view>
  45. </template>
  46. <script>
  47. import { getMbtiLatest } from '@/utils/api'
  48. export default {
  49. data() {
  50. return {
  51. loading: true,
  52. result: null,
  53. canvasContext: null,
  54. canvasWidth: 340,
  55. canvasHeight: 340,
  56. dimLabels: { body: '身', mind: '心', wisdom: '智', action: '行', wealth: '富' },
  57. dimColors: { body: '#FF8C42', mind: '#FF6B9D', wisdom: '#6366F1', action: '#10B981', wealth: '#F59E0B' },
  58. profileFields: {
  59. strengths: { label: '优势' },
  60. weaknesses: { label: '挑战' },
  61. career_tendency: { label: '职业倾向' },
  62. family_style: { label: '家庭风格' },
  63. pairing_notes: { label: '搭配建议' }
  64. }
  65. }
  66. },
  67. computed: {
  68. energyMap() {
  69. return this.result && this.result.profile && this.result.profile.energySuggestion
  70. ? this.result.profile.energySuggestion
  71. : {}
  72. }
  73. },
  74. onLoad(options) {
  75. this.loadResult()
  76. },
  77. methods: {
  78. loadResult() {
  79. var self = this
  80. getMbtiLatest().then(function(res) {
  81. if (res.code === 200 && res.data) self.result = res.data
  82. self.loading = false
  83. self.drawRadar()
  84. }).catch(function() { self.loading = false })
  85. },
  86. onCanvasReady(res) {
  87. if (!res || !res[0] || !res[0].node) return
  88. var ctx = res[0].node.getContext('2d')
  89. var dpr = uni.getWindowInfo().pixelRatio
  90. this.canvasContext = ctx
  91. this.canvasContext.scale(dpr, dpr)
  92. this.drawRadar()
  93. },
  94. drawRadar() {
  95. if (!this.canvasContext || !this.result) return
  96. var ctx = this.canvasContext
  97. var w = this.canvasWidth, h = this.canvasHeight
  98. var cx = w / 2, cy = h / 2, r = w * 0.34
  99. var angleStep = (Math.PI * 2) / 4
  100. ctx.clearRect(0, 0, w, h)
  101. ctx.fillStyle = '#FFF8F0'; ctx.fillRect(0, 0, w, h)
  102. for (var layer = 1; layer <= 3; layer++) {
  103. ctx.beginPath()
  104. for (var g = 0; g < 4; g++) {
  105. var a = g * angleStep - Math.PI / 2
  106. var gx = cx + r * (layer / 3) * Math.cos(a)
  107. var gy = cy + r * (layer / 3) * Math.sin(a)
  108. if (g === 0) ctx.moveTo(gx, gy); else ctx.lineTo(gx, gy)
  109. }
  110. ctx.closePath()
  111. ctx.strokeStyle = 'rgba(99,102,241,0.15)'; ctx.lineWidth = 1; ctx.stroke()
  112. }
  113. var labels = ['E/I', 'S/N', 'T/F', 'J/P']
  114. var colors = ['#6366F1', '#10B981', '#FF6B9D', '#F59E0B']
  115. for (var j = 0; j < 4; j++) {
  116. var a2 = j * angleStep - Math.PI / 2
  117. var lx = cx + r * Math.cos(a2), ly = cy + r * Math.sin(a2)
  118. ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(lx, ly)
  119. ctx.strokeStyle = colors[j] + '44'; ctx.lineWidth = 1; ctx.stroke()
  120. ctx.fillStyle = colors[j]; ctx.font = 'bold 13px sans-serif'
  121. ctx.textAlign = 'center'; ctx.textBaseline = 'middle'
  122. ctx.fillText(labels[j], lx, ly)
  123. }
  124. var scores = {
  125. 'E/I': this.result.eiScore != null ? Math.max(-50, Math.min(50, this.result.eiScore)) : 0,
  126. 'S/N': this.result.snScore != null ? Math.max(-50, Math.min(50, this.result.snScore)) : 0,
  127. 'T/F': this.result.tfScore != null ? Math.max(-50, Math.min(50, this.result.tfScore)) : 0,
  128. 'J/P': this.result.jpScore != null ? Math.max(-50, Math.min(50, this.result.jpScore)) : 0
  129. }
  130. var keys = ['E/I', 'S/N', 'T/F', 'J/P']
  131. ctx.beginPath()
  132. for (var m = 0; m < 4; m++) {
  133. var ang = m * angleStep - Math.PI / 2
  134. var ratio = (scores[keys[m]] + 50) / 100
  135. var dataR = r * ratio
  136. var dx = cx + dataR * Math.cos(ang), dy = cy + dataR * Math.sin(ang)
  137. if (m === 0) ctx.moveTo(dx, dy); else ctx.lineTo(dx, dy)
  138. }
  139. ctx.closePath()
  140. ctx.fillStyle = 'rgba(99,102,241,0.2)'; ctx.fill()
  141. ctx.strokeStyle = '#6366F1'; ctx.lineWidth = 2; ctx.stroke()
  142. for (var n = 0; n < 4; n++) {
  143. var ang2 = n * angleStep - Math.PI / 2
  144. var ratio2 = (scores[keys[n]] + 50) / 100
  145. var dataR2 = r * ratio2
  146. var px = cx + dataR2 * Math.cos(ang2), py = cy + dataR2 * Math.sin(ang2)
  147. ctx.beginPath(); ctx.arc(px, py, 4, 0, Math.PI * 2)
  148. ctx.fillStyle = colors[n]; ctx.fill()
  149. }
  150. },
  151. goProfile() { uni.navigateTo({ url: '/pages/family/mbti-profile' }) },
  152. retake() { uni.navigateTo({ url: '/pages/family/mbti-entry' }) }
  153. }
  154. }
  155. </script>
  156. <style scoped>
  157. .mbr-page { min-height: 100vh; background: #F5FAFE; padding: 24rpx 30rpx 120rpx; box-sizing: border-box }
  158. .mbr-loading { display: flex; justify-content: center; align-items: center; height: 50vh }
  159. .mbr-hero { border-radius: 24rpx; padding: 40rpx 30rpx; margin-bottom: 24rpx; background: linear-gradient(135deg, #6366F1, #818CF8); box-shadow: 0 6rpx 20rpx rgba(99,102,241,0.25) }
  160. .mbr-hero-inner { display: flex; flex-direction: column; align-items: center }
  161. .mbr-type-code { font-size: 88rpx; font-weight: 700; color: #fff; letter-spacing: 8rpx }
  162. .mbr-type-name { font-size: 32rpx; color: rgba(255,255,255,0.9); margin-top: 8rpx }
  163. .mbr-type-desc { font-size: 24rpx; color: rgba(255,255,255,0.8); margin-top: 16rpx; text-align: center; max-width: 600rpx }
  164. .mbr-section { background: #fff; border-radius: 24rpx; padding: 30rpx 26rpx; margin-bottom: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05) }
  165. .mbr-section-title { font-size: 30rpx; font-weight: 600; color: #333; margin-bottom: 20rpx }
  166. .mbr-canvas-wrap { display: flex; justify-content: center }
  167. .mbr-canvas { width: 340px; height: 340px }
  168. .mbr-profile-list { display: flex; flex-direction: column }
  169. .mbr-profile-item { padding: 20rpx 0; border-top: 1rpx solid #F0F0F0 }
  170. .mbr-profile-item:first-child { border-top: none }
  171. .mbr-profile-label { display: block; font-size: 24rpx; color: #999; margin-bottom: 8rpx }
  172. .mbr-profile-value { font-size: 26rpx; color: #333; line-height: 1.5 }
  173. .mbr-energy-list { display: flex; flex-direction: column; gap: 16rpx }
  174. .mbr-energy-item { padding: 20rpx; background: #F5FAFE; border-radius: 16rpx }
  175. .mbr-energy-head { display: flex; align-items: center; margin-bottom: 8rpx }
  176. .mbr-energy-dot { width: 16rpx; height: 16rpx; border-radius: 50%; margin-right: 10rpx }
  177. .mbr-energy-label { font-size: 26rpx; font-weight: 600 }
  178. .mbr-energy-text { font-size: 24rpx; color: #555; line-height: 1.5 }
  179. .mbr-footer { position: fixed; bottom: 0; left: 0; right: 0; padding: 20rpx 30rpx; background: #fff; border-top: 1rpx solid #F0F0F0; display: flex; gap: 20rpx; box-sizing: border-box }
  180. .mbr-btn { flex: 1; height: 88rpx; line-height: 88rpx; font-size: 30rpx; border-radius: 44rpx; border: none; text-align: center }
  181. .mbr-btn-primary { background: linear-gradient(135deg, #6366F1, #818CF8); color: #fff; font-weight: 600 }
  182. .mbr-btn-outline { background: #fff; color: #6366F1; border: 2rpx solid #6366F1 }
  183. </style>