| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672 |
- <template>
- <view class="scr-page">
- <!-- Loading -->
- <view class="scr-loading" v-if="loading">
- <text class="scr-loading-text">加载中...</text>
- </view>
- <template v-else>
- <!-- 总分横幅 -->
- <view class="scr-hero">
- <view class="scr-hero-badge">
- <text class="scr-hero-title">五维幸福总分</text>
- <view class="scr-hero-score-row">
- <text class="scr-hero-score">{{ result.totalScore }}</text>
- <text class="scr-hero-total">/45</text>
- </view>
- <view class="scr-hero-level" :class="'level-' + overallLevel">
- <text>{{ overallLevelName }}</text>
- </view>
- </view>
- <text class="scr-hero-date" v-if="result.createdAt">自检时间:{{ formatDate(result.createdAt) }}</text>
- </view>
- <!-- 雷达图 -->
- <view class="scr-card">
- <view class="scr-card-title">
- <text class="scr-card-title-text">五维能量雷达</text>
- <text class="scr-card-sub">身·智·富·行·心</text>
- </view>
- <view class="scr-canvas-wrap">
- <canvas
- type="2d"
- id="selfCheckRadar"
- class="scr-canvas"
- @ready="onCanvasReady"
- ></canvas>
- </view>
- </view>
- <!-- 维度得分 -->
- <view class="scr-card">
- <view class="scr-card-title">
- <text class="scr-card-title-text">维度得分</text>
- <text class="scr-card-sub">满分9分/维</text>
- </view>
- <view class="scr-dim-list">
- <view class="scr-dim-item" v-for="dim in result.dimensions" :key="dim.dimension">
- <view class="scr-dim-head">
- <view class="scr-dim-label">
- <view class="scr-dim-dot" :style="{ background: dim.color }"></view>
- <text class="scr-dim-name">{{ dim.name }}</text>
- <text class="scr-dim-element">{{ dim.element }}</text>
- </view>
- <view class="scr-dim-score-wrap">
- <text class="scr-dim-score" :style="{ color: dim.color }">{{ dim.score }}</text>
- <text class="scr-dim-score-max">/9</text>
- </view>
- </view>
- <view class="scr-dim-bar">
- <view class="scr-dim-bar-fill" :style="{ width: (dim.score / 9 * 100) + '%', background: dim.color }"></view>
- </view>
- <view class="scr-dim-foot">
- <text class="scr-dim-level" :class="'lvl-' + dim.level">{{ dim.levelName }}</text>
- <text class="scr-dim-summary">{{ dim.summary }}</text>
- </view>
- </view>
- </view>
- </view>
- <!-- 五行相生寻源建议(P0-2) -->
- <view class="scr-card" v-if="result.advices && result.advices.length > 0">
- <view class="scr-card-title">
- <text class="scr-card-title-text">五行相生 · 寻源建议</text>
- <text class="scr-card-sub">低分维度 {{ result.advices.length }} 项</text>
- </view>
- <view class="scr-advice-intro">低于6分的维度,先别硬补——从"生"它的上游借力,同时避开"克"它的压制源:</view>
- <view class="scr-advice-item" v-for="adv in result.advices" :key="adv.dimension">
- <!-- 低分维度 -->
- <view class="scr-advice-dim-row">
- <view class="scr-advice-dim-badge" :style="{ background: adv.color }">
- <text class="scr-advice-dim-name">{{ adv.dimensionName }}</text>
- <text class="scr-advice-dim-elem">{{ adv.element }}</text>
- </view>
- <text class="scr-advice-dim-score">{{ adv.score }}分</text>
- </view>
- <!-- 相生寻源 -->
- <view class="scr-advice-flow">
- <view class="scr-flow-node">
- <view class="scr-flow-dot" :style="{ background: adv.upstreamColor }"></view>
- <text class="scr-flow-label">上游 · {{ adv.upstreamName }}({{ adv.upstreamElement }})</text>
- </view>
- <text class="scr-flow-arrow">⟶</text>
- <text class="scr-flow-action">{{ adv.upstreamReason }}</text>
- </view>
- <!-- 相克寻源 -->
- <view class="scr-advice-flow">
- <view class="scr-flow-node">
- <view class="scr-flow-dot" :style="{ background: adv.restrainerColor }"></view>
- <text class="scr-flow-label">压制 · {{ adv.restrainerName }}({{ adv.restrainerElement }})</text>
- </view>
- <text class="scr-flow-arrow">⛔</text>
- <text class="scr-flow-action">{{ adv.restrainerReason }}</text>
- </view>
- <!-- 行动建议 -->
- <view class="scr-advice-action">
- <text class="scr-advice-action-label">本周行动</text>
- <text class="scr-advice-action-text">{{ adv.action }}</text>
- </view>
- </view>
- </view>
- <!-- 全部健康提示 -->
- <view class="scr-card" v-else>
- <view class="scr-all-healthy">
- <text class="scr-all-healthy-icon">🌿</text>
- <text class="scr-all-healthy-text">五维状态均健康,请继续保持这份平衡!</text>
- </view>
- </view>
- <!-- 历史与重测 -->
- <view class="scr-footer">
- <button class="scr-btn scr-btn-primary" @click="goHistory">历史记录</button>
- <button class="scr-btn scr-btn-outline" @click="retake">重新自检</button>
- </view>
- </template>
- </view>
- </template>
- <script>
- import { getSelfCheckLatest, getSelfCheckHistory } from '@/utils/api'
- import { parseDate } from '@/utils/format.js'
- export default {
- data() {
- return {
- loading: true,
- result: null,
- selfCheckId: '',
- canvasContext: null,
- canvasWidth: 340,
- canvasHeight: 340,
- // 雷达图轴序(对应 tianpan 木火土金水,顺时针)
- axes: ['木', '火', '土', '金', '水'],
- elementKeys: ['action', 'mind', 'body', 'wisdom', 'wealth'],
- elementColors: ['#10B981', '#FF6B9D', '#FF8C42', '#6366F1', '#F59E0B'],
- levelMap: {
- healthy: '健康',
- attention: '留意',
- tense: '紧绷'
- }
- }
- },
- computed: {
- overallLevel: function() {
- var total = this.result ? (this.result.totalScore || 0) : 0
- if (total >= 35) return 'healthy'
- if (total >= 20) return 'attention'
- return 'tense'
- },
- overallLevelName: function() {
- return this.levelMap[this.overallLevel] || '留意'
- }
- },
- onLoad: function(options) {
- this.selfCheckId = options && options.id ? options.id : ''
- this.loadResult()
- },
- methods: {
- loadResult: function() {
- var self = this
- // 优先使用刚提交时缓存的结果,避免多一次请求
- var cached = uni.getStorageSync('lastSelfCheckResult')
- if (cached && cached.id) {
- this.result = cached
- this.loading = false
- this.drawRadar()
- return
- }
- getSelfCheckLatest().then(function(res) {
- self.loading = false
- if (res.code === 200 && res.data) {
- self.result = res.data
- self.drawRadar()
- } else {
- uni.showToast({ title: (res && res.message) || '暂无自检记录', icon: 'none' })
- }
- }).catch(function() {
- self.loading = false
- uni.showToast({ title: '加载失败', icon: 'none' })
- })
- },
- onCanvasReady: function(res) {
- if (!res || !res[0] || !res[0].node) {
- return
- }
- var ctx = res[0].node.getContext('2d')
- var dpr = uni.getWindowInfo().pixelRatio
- this.canvasContext = ctx
- this.canvasContext.scale(dpr, dpr)
- this.drawRadar()
- },
- drawRadar: function() {
- if (!this.canvasContext || !this.result) {
- return
- }
- var ctx = this.canvasContext
- var w = this.canvasWidth
- var h = this.canvasHeight
- var cx = w / 2
- var cy = h / 2
- var r = w * 0.34
- var angleStep = (Math.PI * 2) / 5
- // 清空画布
- ctx.clearRect(0, 0, w, h)
- ctx.fillStyle = '#FFF8F0'
- ctx.fillRect(0, 0, w, h)
- // 维度分数映射
- var scores = {}
- var dims = this.result.dimensions || []
- for (var i = 0; i < dims.length; i++) {
- scores[dims[i].dimension] = dims[i].score || 0
- }
- // 背景五边形(三层网格)
- for (var layer = 1; layer <= 3; layer++) {
- ctx.beginPath()
- for (var g = 0; g < 5; g++) {
- var gAngle = g * angleStep - Math.PI / 2
- var gx = cx + r * (layer / 3) * Math.cos(gAngle)
- var gy = cy + r * (layer / 3) * Math.sin(gAngle)
- if (g === 0) {
- ctx.moveTo(gx, gy)
- } else {
- ctx.lineTo(gx, gy)
- }
- }
- ctx.closePath()
- ctx.strokeStyle = 'rgba(249, 114, 22, 0.15)'
- ctx.lineWidth = 1
- ctx.stroke()
- }
- // 轴线 + 标签
- for (var j = 0; j < 5; j++) {
- var a2 = j * angleStep - Math.PI / 2
- var lx = cx + r * Math.cos(a2)
- var ly = cy + r * Math.sin(a2)
- ctx.beginPath()
- ctx.moveTo(cx, cy)
- ctx.lineTo(lx, ly)
- ctx.strokeStyle = this.elementColors[j] + '44'
- ctx.lineWidth = 1
- ctx.stroke()
- var labelX = cx + (r + 18) * Math.cos(a2)
- var labelY = cy + (r + 18) * Math.sin(a2)
- ctx.fillStyle = this.elementColors[j]
- ctx.font = 'bold 13px sans-serif'
- ctx.textAlign = 'center'
- ctx.textBaseline = 'middle'
- ctx.fillText(this.axes[j], labelX, labelY)
- }
- // 数据驱动多边形
- ctx.beginPath()
- for (var m = 0; m < 5; m++) {
- var ang = m * angleStep - Math.PI / 2
- var key = this.elementKeys[m]
- var ratio = (scores[key] || 0) / 9
- var dataR = r * ratio
- var dx = cx + dataR * Math.cos(ang)
- var dy = cy + dataR * Math.sin(ang)
- if (m === 0) {
- ctx.moveTo(dx, dy)
- } else {
- ctx.lineTo(dx, dy)
- }
- }
- ctx.closePath()
- ctx.fillStyle = 'rgba(249, 114, 22, 0.2)'
- ctx.fill()
- ctx.strokeStyle = '#F97316'
- ctx.lineWidth = 2
- ctx.stroke()
- // 数据节点圆点 + 分数标签
- for (var n = 0; n < 5; n++) {
- var ang2 = n * angleStep - Math.PI / 2
- var key2 = this.elementKeys[n]
- var ratio2 = (scores[key2] || 0) / 9
- var dataR2 = r * ratio2
- var px = cx + dataR2 * Math.cos(ang2)
- var py = cy + dataR2 * Math.sin(ang2)
- ctx.beginPath()
- ctx.arc(px, py, 4, 0, Math.PI * 2)
- ctx.fillStyle = this.elementColors[n]
- ctx.fill()
- var pctLabelR = dataR2 + (ratio2 > 0.35 ? -16 : 18)
- var pLabelX = cx + pctLabelR * Math.cos(ang2)
- var pLabelY = cy + pctLabelR * Math.sin(ang2)
- ctx.fillStyle = this.elementColors[n]
- ctx.font = 'bold 11px sans-serif'
- ctx.textAlign = 'center'
- ctx.textBaseline = 'middle'
- ctx.fillText(String(scores[key2] || 0), pLabelX, pLabelY)
- }
- },
- formatDate: function(dateStr) {
- if (!dateStr) {
- return ''
- }
- var d = parseDate(dateStr)
- if (!d) return ''
- var pad = function(n) { return n < 10 ? '0' + n : '' + n }
- return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes())
- },
- goHistory: function() {
- getSelfCheckHistory().then(function(res) {
- if (res.code === 200 && res.data && res.data.length > 0) {
- uni.showToast({ title: '已生成 ' + res.data.length + ' 条记录', icon: 'none' })
- } else {
- uni.showToast({ title: '暂无历史记录', icon: 'none' })
- }
- }).catch(function() {
- uni.showToast({ title: '暂无历史记录', icon: 'none' })
- })
- },
- retake: function() {
- uni.redirectTo({ url: '/pages/family/self-check' })
- }
- }
- }
- </script>
- <style scoped>
- .scr-page {
- min-height: 100vh;
- background: #F5FAFE;
- padding: 24rpx 30rpx 120rpx;
- box-sizing: border-box;
- }
- .scr-loading {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 60vh;
- }
- .scr-loading-text {
- font-size: 28rpx;
- color: #999;
- }
- /* 总分横幅 */
- .scr-hero {
- background: linear-gradient(135deg, #F97316, #FF8C42);
- border-radius: 24rpx;
- padding: 40rpx 30rpx;
- margin-bottom: 24rpx;
- box-shadow: 0 6rpx 20rpx rgba(249, 114, 22, 0.25);
- }
- .scr-hero-badge {
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .scr-hero-title {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.9);
- }
- .scr-hero-score-row {
- display: flex;
- align-items: baseline;
- margin-top: 10rpx;
- }
- .scr-hero-score {
- font-size: 88rpx;
- font-weight: 700;
- color: #fff;
- }
- .scr-hero-total {
- font-size: 30rpx;
- color: rgba(255, 255, 255, 0.7);
- margin-left: 6rpx;
- }
- .scr-hero-level {
- margin-top: 14rpx;
- padding: 8rpx 28rpx;
- border-radius: 24rpx;
- font-size: 26rpx;
- font-weight: 600;
- background: rgba(255, 255, 255, 0.92);
- }
- .scr-hero-level.level-healthy { color: #2E7D32; }
- .scr-hero-level.level-attention { color: #E65100; }
- .scr-hero-level.level-tense { color: #C62828; }
- .scr-hero-date {
- display: block;
- text-align: center;
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.8);
- margin-top: 16rpx;
- }
- /* 通用卡片 */
- .scr-card {
- background: #fff;
- border-radius: 24rpx;
- padding: 30rpx 26rpx;
- margin-bottom: 24rpx;
- box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
- }
- .scr-card-title {
- display: flex;
- align-items: baseline;
- justify-content: space-between;
- margin-bottom: 24rpx;
- }
- .scr-card-title-text {
- font-size: 32rpx;
- font-weight: 600;
- color: #333;
- }
- .scr-card-sub {
- font-size: 22rpx;
- color: #999;
- }
- /* 雷达图 */
- .scr-canvas-wrap {
- display: flex;
- justify-content: center;
- }
- .scr-canvas {
- width: 340px;
- height: 340px;
- }
- /* 维度得分 */
- .scr-dim-list {
- display: flex;
- flex-direction: column;
- }
- .scr-dim-item {
- margin-bottom: 28rpx;
- }
- .scr-dim-item:last-child {
- margin-bottom: 0;
- }
- .scr-dim-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .scr-dim-label {
- display: flex;
- align-items: center;
- }
- .scr-dim-dot {
- width: 28rpx;
- height: 28rpx;
- border-radius: 50%;
- margin-right: 12rpx;
- }
- .scr-dim-name {
- font-size: 30rpx;
- font-weight: 600;
- color: #333;
- }
- .scr-dim-element {
- font-size: 22rpx;
- color: #999;
- margin-left: 10rpx;
- }
- .scr-dim-score-wrap {
- display: flex;
- align-items: baseline;
- }
- .scr-dim-score {
- font-size: 40rpx;
- font-weight: 700;
- }
- .scr-dim-score-max {
- font-size: 22rpx;
- color: #BBB;
- }
- .scr-dim-bar {
- height: 12rpx;
- background: #F0F0F0;
- border-radius: 6rpx;
- margin-top: 14rpx;
- overflow: hidden;
- }
- .scr-dim-bar-fill {
- height: 100%;
- border-radius: 6rpx;
- transition: width 0.5s ease;
- }
- .scr-dim-foot {
- display: flex;
- align-items: center;
- margin-top: 12rpx;
- }
- .scr-dim-level {
- font-size: 22rpx;
- padding: 4rpx 16rpx;
- border-radius: 16rpx;
- margin-right: 14rpx;
- flex-shrink: 0;
- }
- .scr-dim-level.lvl-healthy { background: #E8F5E9; color: #2E7D32; }
- .scr-dim-level.lvl-attention { background: #FFF8E1; color: #F57F17; }
- .scr-dim-level.lvl-tense { background: #FFEBEE; color: #C62828; }
- .scr-dim-summary {
- flex: 1;
- font-size: 24rpx;
- color: #888;
- line-height: 1.4;
- }
- /* 寻源建议 */
- .scr-advice-intro {
- font-size: 24rpx;
- color: #666;
- line-height: 1.6;
- background: #FFF7ED;
- border-radius: 12rpx;
- padding: 16rpx 20rpx;
- margin-bottom: 24rpx;
- }
- .scr-advice-item {
- border: 2rpx solid #F0E6DC;
- border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 20rpx;
- }
- .scr-advice-item:last-child {
- margin-bottom: 0;
- }
- .scr-advice-dim-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 18rpx;
- }
- .scr-advice-dim-badge {
- display: flex;
- align-items: center;
- padding: 8rpx 20rpx;
- border-radius: 20rpx;
- }
- .scr-advice-dim-name {
- font-size: 28rpx;
- font-weight: 700;
- color: #fff;
- margin-right: 8rpx;
- }
- .scr-advice-dim-elem {
- font-size: 22rpx;
- color: rgba(255, 255, 255, 0.9);
- }
- .scr-advice-dim-score {
- font-size: 26rpx;
- font-weight: 600;
- color: #C62828;
- }
- .scr-advice-flow {
- display: flex;
- align-items: flex-start;
- margin-bottom: 12rpx;
- }
- .scr-flow-node {
- display: flex;
- align-items: center;
- flex-shrink: 0;
- width: 200rpx;
- }
- .scr-flow-dot {
- width: 20rpx;
- height: 20rpx;
- border-radius: 50%;
- margin-right: 8rpx;
- flex-shrink: 0;
- }
- .scr-flow-label {
- font-size: 24rpx;
- font-weight: 600;
- color: #333;
- }
- .scr-flow-arrow {
- flex-shrink: 0;
- font-size: 24rpx;
- color: #F97316;
- margin: 0 10rpx;
- }
- .scr-flow-action {
- flex: 1;
- font-size: 24rpx;
- color: #666;
- line-height: 1.5;
- }
- .scr-advice-action {
- display: flex;
- align-items: flex-start;
- background: #F5FAFE;
- border-radius: 12rpx;
- padding: 16rpx 20rpx;
- margin-top: 10rpx;
- }
- .scr-advice-action-label {
- flex-shrink: 0;
- font-size: 22rpx;
- font-weight: 600;
- color: #F97316;
- margin-right: 12rpx;
- padding: 2rpx 0;
- }
- .scr-advice-action-text {
- flex: 1;
- font-size: 24rpx;
- color: #333;
- line-height: 1.5;
- }
- /* 全部健康 */
- .scr-all-healthy {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 30rpx 0;
- }
- .scr-all-healthy-icon {
- font-size: 72rpx;
- margin-bottom: 16rpx;
- }
- .scr-all-healthy-text {
- font-size: 28rpx;
- color: #666;
- }
- /* 底部按钮 */
- .scr-footer {
- display: flex;
- gap: 20rpx;
- margin-top: 20rpx;
- }
- .scr-btn {
- flex: 1;
- height: 88rpx;
- line-height: 88rpx;
- font-size: 30rpx;
- border-radius: 44rpx;
- text-align: center;
- border: none;
- }
- .scr-btn-primary {
- background: linear-gradient(135deg, #F97316, #FF8C42);
- color: #fff;
- font-weight: 600;
- }
- .scr-btn-outline {
- background: #fff;
- color: #F97316;
- border: 2rpx solid #F97316;
- }
- </style>
|