|
|
@@ -15,25 +15,7 @@
|
|
|
<view class="radar-section" v-if="healthData && healthData.dimensions">
|
|
|
<view class="radar-title">七维健康雷达</view>
|
|
|
<view class="radar-wrap">
|
|
|
- <view class="radar-center">
|
|
|
- <view class="radar-grid">
|
|
|
- <view class="grid-ring ring-3"></view>
|
|
|
- <view class="grid-ring ring-2"></view>
|
|
|
- <view class="grid-ring ring-1"></view>
|
|
|
- </view>
|
|
|
- <view class="radar-spokes">
|
|
|
- <view
|
|
|
- class="spoke-item"
|
|
|
- v-for="(dim, idx) in healthData.dimensions"
|
|
|
- :key="dim.dimension"
|
|
|
- :style="spokeStyles[idx]"
|
|
|
- >
|
|
|
- <view class="spoke-dot" :class="'level-' + dim.level"></view>
|
|
|
- <view class="spoke-label">{{ dim.label }}</view>
|
|
|
- <view class="spoke-score">{{ dim.score || '--' }}</view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
+ <canvas canvas-id="radarCanvas" class="radar-canvas"></canvas>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
@@ -169,24 +151,7 @@ export default {
|
|
|
]
|
|
|
}
|
|
|
},
|
|
|
- computed: {
|
|
|
- spokeStyles: function() {
|
|
|
- if (!this.healthData || !this.healthData.dimensions) return []
|
|
|
- var cache = []
|
|
|
- var dims = this.healthData.dimensions
|
|
|
- for (var i = 0; i < dims.length; i++) {
|
|
|
- var angle = i * 51.4 - 90
|
|
|
- var radius = 85
|
|
|
- var normalizedScore = (dims[i].score || 0) / 100
|
|
|
- var translateY = -radius * normalizedScore
|
|
|
- cache.push({
|
|
|
- transform: 'rotate(' + angle + 'deg) translateY(' + translateY + 'px)',
|
|
|
- '-webkit-transform': 'rotate(' + angle + 'deg) translateY(' + translateY + 'px)'
|
|
|
- })
|
|
|
- }
|
|
|
- return cache
|
|
|
- }
|
|
|
- },
|
|
|
+ computed: {},
|
|
|
onLoad: function(options) {
|
|
|
var childId = options.childId || uni.getStorageSync('currentChildId')
|
|
|
var childName = options.name || ''
|
|
|
@@ -210,12 +175,102 @@ export default {
|
|
|
self.loading = false
|
|
|
if (res.code === 200 && res.data) {
|
|
|
self.healthData = res.data
|
|
|
+ self.$nextTick(function() {
|
|
|
+ self.drawRadarChart()
|
|
|
+ })
|
|
|
}
|
|
|
}).catch(function(e) {
|
|
|
self.loading = false
|
|
|
console.log('获取维度数据失败', e)
|
|
|
})
|
|
|
},
|
|
|
+ drawRadarChart: function() {
|
|
|
+ var dims = this.healthData && this.healthData.dimensions
|
|
|
+ if (!dims || dims.length === 0) return
|
|
|
+ var ctx = uni.createCanvasContext('radarCanvas', this)
|
|
|
+ var w = 440
|
|
|
+ var h = 440
|
|
|
+ var cx = w / 2
|
|
|
+ var cy = h / 2
|
|
|
+ var maxR = 150
|
|
|
+ var count = dims.length
|
|
|
+ var angleStep = (Math.PI * 2) / count
|
|
|
+
|
|
|
+ ctx.clearRect(0, 0, w, h)
|
|
|
+
|
|
|
+ for (var ri = 1; ri <= 3; ri++) {
|
|
|
+ var rr = (maxR / 3) * ri
|
|
|
+ ctx.beginPath()
|
|
|
+ for (var ai = 0; ai <= count; ai++) {
|
|
|
+ var a = ai * angleStep - Math.PI / 2
|
|
|
+ var px = cx + rr * Math.cos(a)
|
|
|
+ var py = cy + rr * Math.sin(a)
|
|
|
+ if (ai === 0) ctx.moveTo(px, py)
|
|
|
+ else ctx.lineTo(px, py)
|
|
|
+ }
|
|
|
+ ctx.closePath()
|
|
|
+ ctx.setStrokeStyle('#e0e0e0')
|
|
|
+ ctx.setLineWidth(1)
|
|
|
+ ctx.stroke()
|
|
|
+ }
|
|
|
+
|
|
|
+ for (var si = 0; si < count; si++) {
|
|
|
+ var sa = si * angleStep - Math.PI / 2
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(cx, cy)
|
|
|
+ ctx.lineTo(cx + maxR * Math.cos(sa), cy + maxR * Math.sin(sa))
|
|
|
+ ctx.setStrokeStyle('#ddd')
|
|
|
+ ctx.setLineWidth(1)
|
|
|
+ ctx.stroke()
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.beginPath()
|
|
|
+ for (var pi = 0; pi < count; pi++) {
|
|
|
+ var normScore = (dims[pi].score || 0) / 100
|
|
|
+ var pa = pi * angleStep - Math.PI / 2
|
|
|
+ var pR = maxR * normScore
|
|
|
+ var px2 = cx + pR * Math.cos(pa)
|
|
|
+ var py2 = cy + pR * Math.sin(pa)
|
|
|
+ if (pi === 0) ctx.moveTo(px2, py2)
|
|
|
+ else ctx.lineTo(px2, py2)
|
|
|
+ }
|
|
|
+ ctx.closePath()
|
|
|
+ ctx.setFillStyle('rgba(255, 140, 66, 0.15)')
|
|
|
+ ctx.setStrokeStyle('#FF8C42')
|
|
|
+ ctx.setLineWidth(2)
|
|
|
+ ctx.fill()
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ for (var di = 0; di < count; di++) {
|
|
|
+ var da = di * angleStep - Math.PI / 2
|
|
|
+ var dR = maxR * ((dims[di].score || 0) / 100)
|
|
|
+ var dx = cx + dR * Math.cos(da)
|
|
|
+ var dy = cy + dR * Math.sin(da)
|
|
|
+
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(dx, dy, 4, 0, Math.PI * 2)
|
|
|
+ ctx.setFillStyle('#FF8C42')
|
|
|
+ ctx.fill()
|
|
|
+
|
|
|
+ var lR = maxR + 22
|
|
|
+ var lx = cx + lR * Math.cos(da)
|
|
|
+ var ly = cy + lR * Math.sin(da)
|
|
|
+ ctx.setFontSize(10)
|
|
|
+ ctx.setFillStyle('#333')
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('middle')
|
|
|
+ ctx.fillText(dims[di].label, lx, ly)
|
|
|
+
|
|
|
+ var sR = dR > 10 ? dR - 12 : dR + 12
|
|
|
+ var sx = cx + sR * Math.cos(da)
|
|
|
+ var sy = cy + sR * Math.sin(da)
|
|
|
+ ctx.setFontSize(9)
|
|
|
+ ctx.setFillStyle('#666')
|
|
|
+ ctx.fillText((dims[di].score || '--') + '', sx, sy)
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.draw()
|
|
|
+ },
|
|
|
getLevelText: function(level) {
|
|
|
var map = {
|
|
|
'excellent': '优秀',
|
|
|
@@ -350,69 +405,9 @@ export default {
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
}
|
|
|
-.radar-center {
|
|
|
+.radar-canvas {
|
|
|
width: 440rpx;
|
|
|
height: 440rpx;
|
|
|
- position: relative;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-.radar-grid {
|
|
|
- position: absolute;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
-}
|
|
|
-.grid-ring {
|
|
|
- position: absolute;
|
|
|
- border-radius: 50%;
|
|
|
- border: 1px solid #ddd;
|
|
|
- left: 50%;
|
|
|
- top: 50%;
|
|
|
- transform: translate(-50%, -50%);
|
|
|
-}
|
|
|
-.ring-1 { width: 33%; height: 33%; }
|
|
|
-.ring-2 { width: 66%; height: 66%; }
|
|
|
-.ring-3 { width: 100%; height: 100%; }
|
|
|
-.radar-spokes {
|
|
|
- position: absolute;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-.spoke-item {
|
|
|
- position: absolute;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- transform-origin: center center;
|
|
|
- width: 80rpx;
|
|
|
- margin-left: -40rpx;
|
|
|
- top: 10rpx;
|
|
|
-}
|
|
|
-.spoke-dot {
|
|
|
- width: 20rpx;
|
|
|
- height: 20rpx;
|
|
|
- border-radius: 50%;
|
|
|
- background: #FF8C42;
|
|
|
- margin-bottom: 6rpx;
|
|
|
-}
|
|
|
-.spoke-dot.level-excellent { background: #10B981; }
|
|
|
-.spoke-dot.level-good { background: #3B82F6; }
|
|
|
-.spoke-dot.level-fair { background: #F59E0B; }
|
|
|
-.spoke-dot.level-poor { background: #EF4444; }
|
|
|
-.spoke-dot.level-none { background: #ddd; }
|
|
|
-.spoke-label {
|
|
|
- font-size: 22rpx;
|
|
|
- color: #333;
|
|
|
- white-space: nowrap;
|
|
|
-}
|
|
|
-.spoke-score {
|
|
|
- font-size: 20rpx;
|
|
|
- color: #666;
|
|
|
- margin-top: 2rpx;
|
|
|
}
|
|
|
.dimension-list {
|
|
|
padding: 20rpx 30rpx;
|