| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- <template>
- <view class="dd-container">
- <!-- Loading -->
- <view class="dd-loading" v-if="loading">
- <text class="loading-text">加载中...</text>
- </view>
- <template v-else>
- <!-- Dimension Header -->
- <view class="dd-header">
- <view class="dd-score-ring">
- <text class="dd-score-value">{{ currentScore || '--' }}</text>
- <text class="dd-score-label">分</text>
- </view>
- <text class="dd-dim-name">{{ dimLabel }}</text>
- <text class="dd-dim-level" :class="'level-' + (dimLevel || 'fair')">{{ levelText }}</text>
- </view>
- <!-- History Trend Chart (Canvas) -->
- <view class="dd-section">
- <text class="dd-section-title">历史趋势</text>
- <view class="dd-chart-wrap" v-if="history.length > 0">
- <canvas canvas-id="trendCanvas" class="dd-canvas"></canvas>
- </view>
- <view class="dd-empty" v-else>
- <text class="empty-text">暂无历史数据</text>
- <text class="empty-sub">完成评估或上传报告后可查看趋势</text>
- </view>
- </view>
- <!-- Percentile -->
- <view class="dd-section" v-if="percentile !== null">
- <text class="dd-section-title">常模对比</text>
- <view class="dd-percentile-wrap">
- <text class="dd-percentile-value">{{ percentile }}%</text>
- <text class="dd-percentile-label">超过同龄同性别 {{ percentile }}% 的人群</text>
- </view>
- </view>
- <!-- Suggestions -->
- <view class="dd-section" v-if="suggestions.length > 0">
- <text class="dd-section-title">改善建议</text>
- <view
- class="dd-suggestion-item"
- v-for="(s, idx) in suggestions"
- :key="idx"
- >
- <text class="dd-sug-num">{{ idx + 1 }}</text>
- <text class="dd-sug-text">{{ s }}</text>
- </view>
- </view>
- <!-- Related Products -->
- <view class="dd-section" v-if="products.length > 0">
- <text class="dd-section-title">相关商品</text>
- <view
- class="dd-product-item"
- v-for="p in products"
- :key="p.id"
- @click="goProduct(p)"
- >
- <image class="dd-prod-img" :src="p.coverImage || '/static/default-product.png'" mode="aspectFill"></image>
- <view class="dd-prod-info">
- <text class="dd-prod-name">{{ p.name }}</text>
- <text class="dd-prod-price">¥{{ p.price }}</text>
- </view>
- </view>
- </view>
- <!-- Action Buttons -->
- <view class="dd-actions">
- <button class="dd-btn" @click="goQuestionnaire">填写问卷</button>
- <button class="dd-btn dd-btn-outline" @click="goDimensions">返回全景</button>
- </view>
- </template>
- </view>
- </template>
- <script>
- import { getDimensionHistory, getDimensionOverview, getProductsByDomain } from '@/utils/api'
- var SUGGESTIONS = {
- growth: [
- '保证每日充足的蛋白质和钙摄入,牛奶、鸡蛋、豆制品是优质选择',
- '每周至少3次户外活动,跳绳、篮球等纵向运动有助于骨骼发育',
- '定期记录身高体重,关注生长曲线是否在正常范围内',
- '保证充足睡眠,生长激素在夜间深睡期分泌最旺盛'
- ],
- sleep: [
- '建立固定的睡前程序:洗热水澡、读故事、调暗灯光',
- '卧室保持安静、黑暗、凉爽,温度控制在18-22°C',
- '睡前1小时避免使用电子设备,蓝光会抑制褪黑素分泌',
- '保持规律的作息时间,周末与平日差异不超过1小时'
- ],
- vision: [
- '遵循"20-20-20"法则:每20分钟看20英尺外20秒',
- '每日保证至少2小时户外活动,自然光有助于预防近视',
- '保持正确读写姿势:眼离书本一尺、胸离桌沿一拳',
- '每半年进行一次视力检查,建立视力发育档案'
- ],
- immunity: [
- '均衡饮食,确保蛋白质、维生素C、锌等免疫营养素摄入',
- '适度运动增强体质,但避免过度训练导致免疫力下降',
- '保证充足睡眠,睡眠不足会影响免疫细胞活性',
- '注意手部卫生,但不过度消毒,适度接触微生物有助免疫系统发育'
- ],
- nutrition: [
- '每天摄入12种以上食材,每周25种以上,确保营养多样性',
- '减少加工食品和高糖饮料,增加全谷物、蔬菜、水果比例',
- '关注铁、钙、维生素D等易缺乏营养素的摄入',
- '培养自主进食能力,营造愉快的就餐氛围'
- ],
- gut: [
- '增加膳食纤维摄入,全谷物、蔬菜、水果是优质来源',
- '适量补充益生菌食物:酸奶、发酵食品等',
- '保持规律排便习惯,及时响应便意',
- '减少油炸、辛辣等刺激性食物,减轻肠胃负担'
- ],
- exercise: [
- '每天至少60分钟中高强度运动,可分多次完成',
- '运动类型多样化:有氧+力量+柔韧性训练结合',
- '让运动变得有趣:球类游戏、游泳、骑自行车都是好选择',
- '家长以身作则,全家一起运动效果更好'
- ]
- }
- export default {
- data() {
- return {
- loading: true,
- dimension: '',
- childId: '',
- dimLabel: '',
- currentScore: null,
- dimLevel: 'fair',
- percentile: null,
- history: [],
- products: [],
- suggestions: []
- }
- },
- computed: {
- levelText: function() {
- var map = { excellent: '优秀', good: '良好', fair: '一般', poor: '较差' }
- return map[this.dimLevel] || '一般'
- }
- },
- onLoad: function(options) {
- this.dimension = options.dimension || ''
- this.childId = options.childId || uni.getStorageSync('currentChildId') || ''
- this.setDimLabel()
- this.suggestions = SUGGESTIONS[this.dimension] || []
- this.loadData()
- },
- onReady: function() {
- if (this.history.length > 0) {
- this.drawChart()
- }
- },
- methods: {
- setDimLabel: function() {
- var map = {
- growth: '生长发育',
- sleep: '睡眠质量',
- vision: '视力健康',
- immunity: '免疫力',
- nutrition: '营养均衡',
- gut: '肠胃健康',
- exercise: '运动活力'
- }
- this.dimLabel = map[this.dimension] || this.dimension
- },
- loadData: function() {
- var self = this
- self.loading = true
- // Load dimension overview for current score and percentile
- getDimensionOverview({ memberId: this.childId }).then(function(res) {
- if (res.code === 200 && res.data && res.data.dimensions) {
- for (var i = 0; i < res.data.dimensions.length; i++) {
- var dim = res.data.dimensions[i]
- if (dim.dimension === self.dimension) {
- self.currentScore = dim.score
- self.dimLevel = dim.level || 'fair'
- self.percentile = dim.percentile || null
- break
- }
- }
- }
- }).catch(function(e) {
- console.log('获取维度概览失败', e)
- })
- // Load history
- getDimensionHistory({ memberId: this.childId, dimension: this.dimension, limit: 10 }).then(function(res) {
- self.loading = false
- if (res.code === 200 && res.data) {
- self.history = Array.isArray(res.data) ? res.data : (res.data.history || [])
- // Try to draw chart if canvas is ready
- setTimeout(function() {
- self.drawChart()
- }, 100)
- }
- }).catch(function(e) {
- self.loading = false
- console.log('获取历史数据失败', e)
- })
- // Load related products from recommendation
- this.loadProducts()
- },
- loadProducts: function() {
- var self = this
- getProductsByDomain('body', 1, 5).then(function(res) {
- if (res.code === 200 && res.data) {
- var list = res.data.rows || res.data
- self.products = Array.isArray(list) ? list.slice(0, 5) : []
- }
- }).catch(function() {})
- },
- drawChart: function() {
- if (this.history.length === 0) return
- var ctx = uni.createCanvasContext('trendCanvas', this)
- var w = 660
- var h = 280
- var padding = { top: 30, right: 30, bottom: 40, left: 50 }
- var chartW = w - padding.left - padding.right
- var chartH = h - padding.top - padding.bottom
- // Clear
- ctx.setFillStyle('#fff')
- ctx.fillRect(0, 0, w, h)
- // Grid lines
- for (var i = 0; i <= 4; i++) {
- var y = padding.top + (chartH / 4) * i
- ctx.setStrokeStyle('#f0f0f0')
- ctx.setLineWidth(1)
- ctx.beginPath()
- ctx.moveTo(padding.left, y)
- ctx.lineTo(w - padding.right, y)
- ctx.stroke()
- // Label
- var labelVal = 100 - i * 25
- ctx.setFillStyle('#999')
- ctx.setFontSize(20)
- ctx.fillText(labelVal + '', 10, y + 6)
- }
- // Draw data points and line
- var points = []
- var count = this.history.length
- for (var j = 0; j < count; j++) {
- var item = this.history[j]
- var score = item.score || 0
- var x = padding.left + (chartW / (count - 1 || 1)) * j
- var y = padding.top + chartH - (score / 100) * chartH
- points.push({ x: x, y: y, score: score, date: item.date || item.createTime || '' })
- }
- // Line
- if (points.length > 1) {
- ctx.setStrokeStyle('#4A9BD7')
- ctx.setLineWidth(3)
- ctx.beginPath()
- ctx.moveTo(points[0].x, points[0].y)
- for (var k = 1; k < points.length; k++) {
- ctx.lineTo(points[k].x, points[k].y)
- }
- ctx.stroke()
- // Area fill
- ctx.setFillStyle('rgba(74, 155, 215, 0.1)')
- ctx.beginPath()
- ctx.moveTo(points[0].x, padding.top + chartH)
- ctx.lineTo(points[0].x, points[0].y)
- for (var l = 1; l < points.length; l++) {
- ctx.lineTo(points[l].x, points[l].y)
- }
- ctx.lineTo(points[points.length - 1].x, padding.top + chartH)
- ctx.closePath()
- ctx.fill()
- }
- // Dots
- for (var m = 0; m < points.length; m++) {
- ctx.setFillStyle('#4A9BD7')
- ctx.beginPath()
- ctx.arc(points[m].x, points[m].y, 5, 0, 2 * Math.PI)
- ctx.fill()
- ctx.setFillStyle('#fff')
- ctx.beginPath()
- ctx.arc(points[m].x, points[m].y, 2.5, 0, 2 * Math.PI)
- ctx.fill()
- }
- // X-axis labels
- if (points.length > 0) {
- for (var n = 0; n < points.length; n++) {
- var dateStr = this.formatShortDate(points[n].date)
- ctx.setFillStyle('#999')
- ctx.setFontSize(18)
- ctx.setTextAlign('center')
- ctx.fillText(dateStr, points[n].x, h - 8)
- }
- }
- ctx.draw()
- },
- formatShortDate: function(dateStr) {
- if (!dateStr) return ''
- var d = dateStr.toString()
- return d.length >= 10 ? d.substring(5, 10) : d
- },
- goQuestionnaire: function() {
- uni.navigateTo({
- url: '/pages/body/dimension-questionnaire?childId=' + this.childId
- })
- },
- goDimensions: function() {
- uni.navigateTo({
- url: '/pages/body/health-dimensions?childId=' + this.childId
- })
- },
- goProduct: function(product) {
- if (product && product.id) {
- uni.navigateTo({ url: '/pages/shop/detail?id=' + product.id })
- }
- }
- }
- }
- </script>
- <style scoped>
- .dd-container {
- min-height: 100vh;
- background: #F5FAFE;
- padding-bottom: 100rpx;
- }
- .dd-loading {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 60vh;
- }
- .loading-text {
- font-size: 28rpx;
- color: #999;
- }
- .dd-header {
- background: linear-gradient(135deg, #4A9BD7, #63B3ED);
- padding: 60rpx 0;
- text-align: center;
- color: #fff;
- }
- .dd-score-ring {
- width: 160rpx;
- height: 160rpx;
- border: 6rpx solid rgba(255,255,255,0.5);
- border-radius: 50%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- margin: 0 auto 20rpx;
- }
- .dd-score-value {
- font-size: 56rpx;
- font-weight: 700;
- line-height: 1;
- }
- .dd-score-label {
- font-size: 24rpx;
- opacity: 0.8;
- }
- .dd-dim-name {
- font-size: 40rpx;
- font-weight: 600;
- display: block;
- }
- .dd-dim-level {
- font-size: 26rpx;
- padding: 6rpx 24rpx;
- border-radius: 20rpx;
- display: inline-block;
- margin-top: 16rpx;
- background: rgba(255,255,255,0.2);
- }
- .dd-section {
- background: #fff;
- margin: 20rpx 30rpx;
- border-radius: 20rpx;
- padding: 30rpx;
- box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
- }
- .dd-section-title {
- font-size: 32rpx;
- font-weight: 600;
- color: #333;
- margin-bottom: 20rpx;
- display: block;
- }
- .dd-chart-wrap {
- width: 100%;
- height: 300rpx;
- }
- .dd-canvas {
- width: 700rpx;
- height: 300rpx;
- }
- .dd-empty {
- text-align: center;
- padding: 40rpx 0;
- }
- .empty-text {
- font-size: 28rpx;
- color: #999;
- }
- .empty-sub {
- font-size: 24rpx;
- color: #ccc;
- margin-top: 10rpx;
- }
- .dd-percentile-wrap {
- text-align: center;
- padding: 20rpx 0;
- }
- .dd-percentile-value {
- font-size: 48rpx;
- font-weight: 700;
- color: #4A9BD7;
- }
- .dd-percentile-label {
- font-size: 26rpx;
- color: #888;
- margin-top: 10rpx;
- display: block;
- }
- .dd-suggestion-item {
- display: flex;
- align-items: flex-start;
- padding: 16rpx 0;
- border-bottom: 1rpx solid #f5f5f5;
- }
- .dd-suggestion-item:last-child {
- border-bottom: none;
- }
- .dd-sug-num {
- width: 40rpx;
- height: 40rpx;
- background: #4A9BD7;
- color: #fff;
- border-radius: 50%;
- text-align: center;
- line-height: 40rpx;
- font-size: 22rpx;
- flex-shrink: 0;
- margin-right: 16rpx;
- margin-top: 4rpx;
- }
- .dd-sug-text {
- font-size: 28rpx;
- color: #555;
- line-height: 1.6;
- flex: 1;
- }
- .dd-product-item {
- display: flex;
- padding: 20rpx 0;
- border-bottom: 1rpx solid #f5f5f5;
- }
- .dd-product-item:last-child {
- border-bottom: none;
- }
- .dd-prod-img {
- width: 120rpx;
- height: 120rpx;
- border-radius: 12rpx;
- margin-right: 20rpx;
- flex-shrink: 0;
- background: #f0f0f0;
- }
- .dd-prod-info {
- flex: 1;
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
- .dd-prod-name {
- font-size: 28rpx;
- color: #333;
- font-weight: 500;
- }
- .dd-prod-price {
- font-size: 30rpx;
- color: #FF6B35;
- font-weight: 600;
- margin-top: 8rpx;
- }
- .dd-actions {
- display: flex;
- gap: 20rpx;
- margin: 40rpx 30rpx;
- }
- .dd-btn {
- flex: 1;
- height: 88rpx;
- line-height: 88rpx;
- background: linear-gradient(135deg, #4A9BD7, #63B3ED);
- color: #fff;
- font-size: 32rpx;
- border-radius: 44rpx;
- text-align: center;
- }
- .dd-btn-outline {
- background: #fff;
- color: #4A9BD7;
- border: 2rpx solid #4A9BD7;
- }
- </style>
|