Просмотр исходного кода

feat(body): add HealthDimensionsSection component for inline health dimensions display on body page

Xiaogang Liao 4 месяцев назад
Родитель
Сommit
7412c9ad6b
1 измененных файлов с 330 добавлено и 0 удалено
  1. 330 0
      cfc-frontend/components/HealthDimensionsSection.vue

+ 330 - 0
cfc-frontend/components/HealthDimensionsSection.vue

@@ -0,0 +1,330 @@
+<template>
+  <view class="hds-section">
+    <!-- 健康指数 -->
+    <view class="hds-health-index" v-if="healthData && healthData.healthIndex">
+      <view class="index-circle">
+        <text class="index-value">{{ healthData.healthIndex }}</text>
+        <text class="index-label">健康指数</text>
+      </view>
+    </view>
+
+    <!-- 七维健康雷达 -->
+    <view class="hds-radar" 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>
+      </view>
+    </view>
+
+    <!-- 维度列表 -->
+    <view class="hds-dim-list" v-if="healthData && healthData.dimensions">
+      <view
+        class="dim-card"
+        v-for="dim in healthData.dimensions"
+        :key="dim.dimension"
+        @click="goDimDetail(dim)"
+      >
+        <view class="dim-header">
+          <text class="dim-label">{{ dim.label }}</text>
+          <text class="dim-score" :class="'score-' + dim.level">{{ dim.score || '--' }}分</text>
+        </view>
+        <view class="dim-bar-wrap">
+          <view class="dim-bar-bg">
+            <view
+              class="dim-bar-fill"
+              :class="'fill-' + dim.level"
+              :style="{ width: (dim.score || 0) + '%' }"
+            ></view>
+          </view>
+          <text class="dim-norms" v-if="dim.normMin && dim.normMax">
+            正常范围 {{ dim.normMin }}-{{ dim.normMax }}
+          </text>
+        </view>
+        <view class="dim-footer">
+          <text class="dim-level-tag" :class="'tag-' + dim.level">{{ getLevelText(dim.level) }}</text>
+          <text class="dim-source" v-if="dim.trend">{{ dim.trend === 'up' ? '↑ 上升' : dim.trend === 'down' ? '↓ 下降' : '→ 稳定' }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 空数据 -->
+    <view class="hds-empty" v-if="!healthData">
+      <text class="empty-tip">暂无维度数据</text>
+      <text class="empty-sub">上传健康报告或填写问卷后可查看</text>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    healthData: {
+      type: Object,
+      default: null
+    },
+    activeChildId: {
+      type: [Number, String],
+      default: null
+    }
+  },
+  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
+    }
+  },
+  methods: {
+    getLevelText: function(level) {
+      var map = {
+        'excellent': '优秀',
+        'good': '良好',
+        'fair': '一般',
+        'poor': '较差',
+        'none': '暂无数据'
+      }
+      return map[level] || ''
+    },
+    goDimDetail: function(dim) {
+      if (!dim || !dim.dimension) return
+      var childId = this.activeChildId || ''
+      uni.navigateTo({
+        url: '/pages/body-detail/dimension-detail?dimension=' + dim.dimension + '&childId=' + childId
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.hds-section {
+  margin: 0 30rpx 20rpx;
+}
+.hds-health-index {
+  display: flex;
+  justify-content: center;
+  padding: 30rpx;
+  background: #fff;
+  border-radius: 20rpx;
+  margin-bottom: 20rpx;
+}
+.index-circle {
+  width: 160rpx;
+  height: 160rpx;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #FF8C42, #FF6B35);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+}
+.index-value {
+  font-size: 48rpx;
+  font-weight: bold;
+  line-height: 1;
+}
+.index-label {
+  font-size: 22rpx;
+  opacity: 0.9;
+  margin-top: 8rpx;
+}
+.hds-radar {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+}
+.radar-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 30rpx;
+  text-align: center;
+}
+.radar-wrap {
+  display: flex;
+  justify-content: center;
+}
+.radar-center {
+  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;
+}
+.hds-dim-list {
+  padding: 0;
+}
+.dim-card {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+}
+.dim-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+.dim-label {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.dim-score {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #FF8C42;
+}
+.dim-score.score-excellent { color: #10B981; }
+.dim-score.score-good { color: #3B82F6; }
+.dim-score.score-fair { color: #F59E0B; }
+.dim-score.score-poor { color: #EF4444; }
+.dim-bar-wrap {
+  margin-bottom: 12rpx;
+}
+.dim-bar-bg {
+  height: 16rpx;
+  background: #f0f0f0;
+  border-radius: 8rpx;
+  overflow: hidden;
+  margin-bottom: 6rpx;
+}
+.dim-bar-fill {
+  height: 100%;
+  border-radius: 8rpx;
+  background: #FF8C42;
+  transition: width 0.3s;
+}
+.dim-bar-fill.fill-excellent { background: #10B981; }
+.dim-bar-fill.fill-good { background: #3B82F6; }
+.dim-bar-fill.fill-fair { background: #F59E0B; }
+.dim-bar-fill.fill-poor { background: #EF4444; }
+.dim-norms {
+  font-size: 22rpx;
+  color: #999;
+}
+.dim-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.dim-level-tag {
+  font-size: 22rpx;
+  padding: 4rpx 16rpx;
+  border-radius: 20rpx;
+  background: #f0f0f0;
+  color: #666;
+}
+.dim-level-tag.tag-excellent { background: #D1FAE5; color: #10B981; }
+.dim-level-tag.tag-good { background: #DBEAFE; color: #3B82F6; }
+.dim-level-tag.tag-fair { background: #FEF3C7; color: #F59E0B; }
+.dim-level-tag.tag-poor { background: #FEE2E2; color: #EF4444; }
+.dim-source {
+  font-size: 22rpx;
+  color: #999;
+}
+.hds-empty {
+  padding: 80rpx 30rpx;
+  text-align: center;
+}
+.hds-empty .empty-tip {
+  font-size: 30rpx;
+  color: #999;
+  display: block;
+  margin-bottom: 16rpx;
+}
+.hds-empty .empty-sub {
+  font-size: 24rpx;
+  color: #ccc;
+  display: block;
+}
+</style>