Files
digital-psychology/apps/user-h5/src/components/rhythm/RhythmResult.vue
T
jackyu66gitandCursor e735aa43e3 refactor(ECR-001): Phase F types/sdk 对齐并拆分超标 H5 页
抽出 OpenAPI DTO;将 Ask/Report/Profile/Star 等 8 页拆到 ≤400 行,并修 ScaleSummary、fortuneKey、Scale 选答与单测。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 18:19:34 +08:00

236 lines
6.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div>
<div class="mode-tabs" role="tablist">
<button
v-for="t in resultTabs"
:key="t.key"
type="button"
class="mode-tab"
:class="{ on: tab === t.key }"
@click="$emit('update:tab', t.key)"
>
{{ t.label }}
</button>
</div>
<template v-if="tab === 'overview'">
<h2 class="hl">{{ headline || '我的身心节律' }}</h2>
<p v-if="oneLiner" class="lead">{{ oneLiner }}</p>
<div v-if="keywords.length" class="tags">
<span v-for="k in keywords" :key="k" class="tag">{{ k }}</span>
</div>
<div v-if="styleLabel" class="spotlight">
<strong>节律倾向</strong>
<p>{{ styleLabel }}</p>
</div>
<p v-if="overviewText" class="overview">{{ overviewText }}</p>
</template>
<template v-else-if="tab === 'wuxing'">
<WuxingBars v-if="wuxingBars.length" :bars="wuxingBars" />
<div v-else class="empty-soft">
<HomeToolIcon name="rhythm" :size="48" />
<p>暂无五行分布数据可从概览与建议了解节律倾向</p>
</div>
<ReportRich :summary="summary" :detail="null" :deep="false" />
</template>
<template v-else-if="tab === 'tips'">
<ReportRich :summary="summary" :detail="detail" :deep="!!report.has_deep_access" />
<div v-if="!report.has_deep_access" class="lock-card">
<p>完整习惯方案与本周练习可在深度版或成长会员中查看</p>
<button class="cta soft" type="button" :disabled="paying" @click="$emit('buyDeep')">解锁深度版</button>
</div>
</template>
<template v-else>
<router-link class="term-card" to="/companion">
<div class="term-ico" aria-hidden="true">
<HomeToolIcon name="companion" :size="40" />
</div>
<div class="term-body">
<strong>节气生活陪伴</strong>
<p>{{ solarTip || '结合当季节气,微调作息与饮食节奏。' }}</p>
</div>
<span class="term-go" aria-hidden="true"></span>
</router-link>
<p v-if="weekFocus" class="focus-tip">本周关注{{ weekFocus }}</p>
</template>
<div class="sticky-bar">
<router-link class="sb-btn primary" to="/companion">去节气陪伴</router-link>
<button
v-if="!report.has_deep_access"
class="sb-btn"
type="button"
:disabled="paying"
@click="$emit('buyDeep')"
>
解锁深度
</button>
<router-link class="sb-btn" to="/ask">去问答</router-link>
</div>
</div>
</template>
<script setup lang="ts">
import type { GrowthReport } from '@yuxingu/types'
import HomeToolIcon from '../HomeToolIcon.vue'
import ReportRich from '../ReportRich.vue'
import WuxingBars, { type WuxingBar } from '../WuxingBars.vue'
import { resultTabs } from '../../composables/useLifeRhythmPage'
defineProps<{
tab: 'overview' | 'wuxing' | 'tips' | 'term'
headline: string
oneLiner: string
styleLabel: string
overviewText: string
weekFocus: string
keywords: string[]
solarTip: string
wuxingBars: WuxingBar[]
summary: Record<string, unknown>
detail: Record<string, unknown> | null
report: GrowthReport
paying: boolean
}>()
defineEmits<{
'update:tab': [value: 'overview' | 'wuxing' | 'tips' | 'term']
buyDeep: []
}>()
</script>
<style scoped>
.mode-tabs {
display: flex;
gap: 4px;
margin-bottom: 14px;
background: #fafafa;
border-radius: var(--radius-pill);
padding: 4px;
}
.mode-tab {
flex: 1;
border: none;
background: transparent;
padding: 9px 4px;
border-radius: var(--radius-pill);
font-size: 13px;
color: #888;
font-weight: 600;
}
.mode-tab.on {
background: #fff;
color: var(--color-primary);
box-shadow: 0 2px 8px rgba(229, 77, 66, 0.1);
}
.hl {
font-family: var(--font-display);
font-size: 20px;
font-weight: 700;
line-height: 1.35;
color: #222;
}
.lead { margin-top: 8px; font-size: 13px; color: #777; line-height: 1.55; }
.tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.tag {
font-size: 11px;
padding: 4px 10px;
border-radius: var(--radius-pill);
background: rgba(255, 255, 255, 0.8);
color: #8a4a3a;
font-weight: 600;
border: 1px solid #f5e8e4;
}
.spotlight {
margin-top: 14px;
padding: 14px;
border-radius: var(--radius-xl);
background: linear-gradient(145deg, #fff0ec, #ffe4dc);
box-shadow: var(--shadow-card);
}
.spotlight strong { font-size: 14px; color: #222; }
.spotlight p { margin-top: 6px; font-size: 13px; color: #666; line-height: 1.5; }
.overview { margin-top: 12px; font-size: 13px; color: #555; line-height: 1.65; }
.empty-soft {
text-align: center;
padding: 24px 16px;
border-radius: var(--radius-xl);
background: #fafafa;
color: #999;
font-size: 13px;
line-height: 1.5;
margin-bottom: 12px;
}
.empty-soft p { margin-top: 10px; }
.lock-card {
margin-top: 12px;
padding: 16px;
background: linear-gradient(145deg, #fff8e8, #ffeec8);
border-radius: var(--radius-xl);
}
.cta.soft {
margin-top: 14px;
width: 100%;
padding: 13px;
border: 1.5px solid #f5c4bc;
border-radius: 22px;
color: var(--color-primary);
font-size: 15px;
font-weight: 600;
background: #fff;
box-shadow: none;
}
.cta:disabled { opacity: 0.45; }
.term-card {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
border-radius: var(--radius-xl);
background: linear-gradient(145deg, #fff0ec, #ffe4dc);
box-shadow: var(--shadow-card);
color: inherit;
}
.term-body { flex: 1; min-width: 0; }
.term-body strong { font-size: 15px; color: #222; }
.term-body p { margin-top: 4px; font-size: 12px; color: #666; line-height: 1.45; }
.term-go { font-size: 20px; color: var(--color-primary); }
.focus-tip { margin-top: 12px; font-size: 13px; color: #888; }
.sticky-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
display: flex;
gap: 8px;
padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(12px);
border-top: 1px solid #f0ebe8;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.06);
}
.sb-btn {
flex: 1;
padding: 11px 8px;
border-radius: 22px;
border: 1.5px solid #f0ebe8;
background: #fff;
font-size: 13px;
font-weight: 600;
color: #555;
text-align: center;
text-decoration: none;
}
.sb-btn.primary {
background: linear-gradient(135deg, #ff7a6e, var(--color-primary));
color: #fff;
border: none;
box-shadow: 0 4px 12px rgba(229, 77, 66, 0.25);
}
.sb-btn:disabled { opacity: 0.45; }
</style>