316 lines
13 KiB
Vue
316 lines
13 KiB
Vue
<template>
|
|
<view class="scroll-container theme-light">
|
|
<view class="bt-page">
|
|
<MemberInfoSubNav title="体测报告" @back="onBack" />
|
|
<view v-if="record" class="bt-page__body">
|
|
<view class="bt-score-card">
|
|
<view class="bt-score-card__circle">
|
|
<text class="bt-score-card__num">{{ record.score }}</text>
|
|
<text class="bt-score-card__grade">{{ record.grade }}</text>
|
|
</view>
|
|
<view class="bt-score-card__info">
|
|
<text class="bt-score-card__title">{{ record.gradeLabel }} · {{ record.status }}</text>
|
|
<text class="bt-score-card__date">{{ record.date }} {{ record.time }}</text>
|
|
<text v-if="record.bodyAge" class="bt-score-card__date">
|
|
身体年龄 {{ record.bodyAge }} 岁 · 实际 {{ record.realAge }} 岁
|
|
</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="bt-card">
|
|
<text class="bt-card__title">核心指标</text>
|
|
<view class="bt-metrics">
|
|
<view
|
|
v-for="m in metricCards"
|
|
:key="m.key"
|
|
class="bt-metric"
|
|
>
|
|
<text class="bt-metric__value">{{ m.display }}</text>
|
|
<text class="bt-metric__label">{{ m.label }}</text>
|
|
<text
|
|
v-if="m.changeText"
|
|
class="bt-metric__change"
|
|
:class="m.changeClass"
|
|
>
|
|
较上次 {{ m.changeText }}
|
|
</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="bt-card">
|
|
<text class="bt-card__title">体成分雷达图</text>
|
|
<BodyTestRadarChart
|
|
:labels="radarLabels"
|
|
:values="radarValues"
|
|
:width="chartWidth"
|
|
:height="220"
|
|
/>
|
|
</view>
|
|
|
|
<view class="bt-card">
|
|
<text class="bt-card__title">人体成分分布</text>
|
|
<view class="bt-body-map">
|
|
<view class="bt-body-map__figure">
|
|
<view class="bt-body-map__head"></view>
|
|
<view class="bt-body-map__limbs">
|
|
<view class="bt-body-map__arm"></view>
|
|
<view class="bt-body-map__arm"></view>
|
|
</view>
|
|
<view class="bt-body-map__torso"></view>
|
|
<view class="bt-body-map__legs">
|
|
<view class="bt-body-map__leg"></view>
|
|
<view class="bt-body-map__leg"></view>
|
|
</view>
|
|
</view>
|
|
<view class="bt-body-map__segments">
|
|
<view
|
|
v-for="seg in record.bodySegments"
|
|
:key="seg.part"
|
|
class="bt-body-map__seg"
|
|
:class="'bt-body-map__seg--' + seg.level"
|
|
>
|
|
<text class="bt-body-map__seg-name">{{ seg.part }}</text>
|
|
<text class="bt-body-map__seg-val">{{ seg.value }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="bt-card">
|
|
<text class="bt-card__title">指标变化趋势</text>
|
|
<BodyTestTrendChart
|
|
:points="trendPreview"
|
|
unit="kg"
|
|
:width="chartWidth"
|
|
:height="140"
|
|
/>
|
|
<view
|
|
class="bt-trend-link"
|
|
hover-class="mi-tap--hover"
|
|
:hover-stay-time="150"
|
|
@tap="goTrend"
|
|
>
|
|
<text class="bt-trend-link__text">查看完整趋势分析</text>
|
|
<image
|
|
class="bt-trend-link__arrow"
|
|
src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/chevronright3.png"
|
|
mode="aspectFit"
|
|
/>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="bt-card">
|
|
<text class="bt-card__title">健康建议</text>
|
|
<view class="bt-advice-list">
|
|
<view
|
|
v-for="(tip, idx) in record.advice"
|
|
:key="idx"
|
|
class="bt-advice-item"
|
|
>
|
|
<view class="bt-advice-item__dot"></view>
|
|
<text class="bt-advice-item__text">{{ tip }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="courses.length" class="bt-card">
|
|
<text class="bt-card__title">推荐课程</text>
|
|
<view
|
|
v-for="course in courses"
|
|
:key="course.id"
|
|
class="bt-course"
|
|
hover-class="mi-tap-row--hover"
|
|
:hover-stay-time="150"
|
|
@tap="onCourseTap(course)"
|
|
>
|
|
<image class="bt-course__banner" :src="course.banner" mode="aspectFill" />
|
|
<view class="bt-course__info">
|
|
<text class="bt-course__tag">{{ course.tag }}</text>
|
|
<text class="bt-course__title">{{ course.title }}</text>
|
|
<text class="bt-course__meta">{{ course.coach }} · {{ course.schedule }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="bt-footer-actions">
|
|
<view
|
|
class="bt-btn bt-btn--outline"
|
|
hover-class="mi-tap-btn--hover"
|
|
:hover-stay-time="150"
|
|
@tap="exportReport"
|
|
>
|
|
<image class="bt-btn__icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/filetext.png" mode="aspectFit" />
|
|
<text class="bt-btn__text">导出 PDF</text>
|
|
</view>
|
|
<view
|
|
class="bt-btn bt-btn--outline"
|
|
hover-class="mi-tap-btn--hover"
|
|
:hover-stay-time="150"
|
|
@tap="shareReport"
|
|
>
|
|
<image class="bt-btn__icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/share2.png" mode="aspectFit" />
|
|
<text class="bt-btn__text">分享</text>
|
|
</view>
|
|
<view
|
|
class="bt-btn bt-btn--primary"
|
|
hover-class="mi-tap-btn--hover"
|
|
:hover-stay-time="150"
|
|
@tap="retest"
|
|
>
|
|
<text class="bt-btn__text">再次体测</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view v-else class="bt-empty">
|
|
<text class="bt-empty__text">未找到体测报告</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import MemberInfoSubNav from '@/components/memberInfo/MemberInfoSubNav.vue'
|
|
import BodyTestRadarChart from '@/components/memberInfo/BodyTestRadarChart.vue'
|
|
import BodyTestTrendChart from '@/components/memberInfo/BodyTestTrendChart.vue'
|
|
import { PAGE, navigateToPage, goBackOrTab } from '@/common/constants/routes.js'
|
|
import { loadMemberStore } from '@/common/memberInfo/store.js'
|
|
import {
|
|
getBodyTestRecordById,
|
|
getBodyTestTrendData,
|
|
getRecommendedCourses,
|
|
computeChanges,
|
|
formatChangeValue,
|
|
bodyTestMock
|
|
} from '@/common/memberInfo/bodyTestStore.js'
|
|
|
|
export default {
|
|
components: { MemberInfoSubNav, BodyTestRadarChart, BodyTestTrendChart },
|
|
data() {
|
|
return {
|
|
recordId: null,
|
|
record: null,
|
|
previous: null,
|
|
chartWidth: 300
|
|
}
|
|
},
|
|
computed: {
|
|
radarLabels() {
|
|
return bodyTestMock.radarLabels.map((l) => l.label)
|
|
},
|
|
radarValues() {
|
|
if (!this.record?.radar) return []
|
|
const keys = bodyTestMock.radarLabels.map((l) => l.key)
|
|
return keys.map((k) => this.record.radar[k] || 0)
|
|
},
|
|
metricCards() {
|
|
if (!this.record?.metrics) return []
|
|
const changes = this.changes
|
|
const defs = bodyTestMock.metricDefs.slice(0, 8)
|
|
return defs.map((def) => {
|
|
const val = this.record.metrics[def.key]
|
|
const diff = changes[def.key]
|
|
let changeText = ''
|
|
let changeClass = ''
|
|
if (diff !== undefined && diff !== null) {
|
|
changeText = formatChangeValue(def.key, diff)
|
|
const lowerBetter = ['weight', 'bodyFat', 'visceralFat'].includes(def.key)
|
|
const isGood = lowerBetter ? diff < 0 : diff > 0
|
|
changeClass = isGood ? 'bt-metric__change--down' : 'bt-metric__change--up'
|
|
}
|
|
const display = def.unit ? `${val}${def.unit === '%' ? '%' : def.unit === 'kg' ? '' : ` ${def.unit}`}` : val
|
|
return {
|
|
key: def.key,
|
|
label: def.label + (def.unit && def.unit !== '%' && def.unit !== 'kg' ? `(${def.unit})` : def.unit === 'kg' ? '(kg)' : def.unit === '%' ? '(%)' : ''),
|
|
display: def.key === 'bodyFat' ? `${val}%` : def.key === 'bodyWater' ? `${val}%` : def.unit === 'kg' ? val : def.unit ? `${val}` : val,
|
|
changeText,
|
|
changeClass
|
|
}
|
|
})
|
|
},
|
|
changes() {
|
|
if (this.record?.changes) return this.record.changes
|
|
if (this.previous) return computeChanges(this.record, this.previous)
|
|
return {}
|
|
},
|
|
trendPreview() {
|
|
const store = loadMemberStore()
|
|
return getBodyTestTrendData(store, 'weight', 4)
|
|
},
|
|
courses() {
|
|
return getRecommendedCourses(this.record)
|
|
}
|
|
},
|
|
onLoad(options) {
|
|
this.recordId = options?.id ? Number(options.id) : null
|
|
this.chartWidth = uni.getSystemInfoSync().windowWidth - 64
|
|
this.loadRecord()
|
|
},
|
|
methods: {
|
|
loadRecord() {
|
|
const store = loadMemberStore()
|
|
const records = store.bodyTest.records
|
|
if (this.recordId) {
|
|
this.record = getBodyTestRecordById(store, this.recordId)
|
|
} else {
|
|
this.record = records.length ? { ...records[0] } : null
|
|
}
|
|
if (this.record) {
|
|
const idx = records.findIndex((r) => r.id === this.record.id)
|
|
this.previous = idx >= 0 && records[idx + 1] ? records[idx + 1] : null
|
|
}
|
|
},
|
|
onBack() {
|
|
goBackOrTab(PAGE.BODY_TEST_HOME)
|
|
},
|
|
goTrend() {
|
|
navigateToPage(`${PAGE.BODY_TEST_TREND}?metric=weight`)
|
|
},
|
|
exportReport() {
|
|
uni.showLoading({ title: '生成中…' })
|
|
setTimeout(() => {
|
|
uni.hideLoading()
|
|
uni.showToast({ title: '报告已保存到相册', icon: 'success' })
|
|
}, 1200)
|
|
},
|
|
shareReport() {
|
|
uni.showActionSheet({
|
|
itemList: ['分享给微信好友', '生成分享海报', '复制报告链接'],
|
|
success: (res) => {
|
|
const msgs = ['已唤起微信分享', '海报已生成', '链接已复制']
|
|
uni.showToast({ title: msgs[res.tapIndex] || '分享成功', icon: 'none' })
|
|
}
|
|
})
|
|
},
|
|
onCourseTap(course) {
|
|
uni.showModal({
|
|
title: course.title,
|
|
content: `${course.coach}\n${course.schedule}\n\n是否前往预约?`,
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
navigateToPage(PAGE.COURSE_LIST)
|
|
}
|
|
}
|
|
})
|
|
},
|
|
retest() {
|
|
navigateToPage(PAGE.BODY_TEST_HOME)
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
@import '@/common/style/base.css';
|
|
@import '@/common/style/memberInfo/pages/page-reset.css';
|
|
@import '@/common/style/memberInfo/pages/sub-page-base.css';
|
|
@import '@/common/style/memberInfo/member-info-component-reset.css';
|
|
@import '@/common/style/memberInfo/member-info-sub-nav.css';
|
|
@import '@/common/style/memberInfo/member-info-tap.css';
|
|
@import '@/common/style/memberInfo/pages/body-test-common.css';
|
|
|
|
.bt-footer-actions .bt-btn {
|
|
flex: 1;
|
|
}
|
|
</style>
|