Files
digital-psychology/apps/user-h5/src/pages/PortraitPage.vue
T
jackyu66gitandCursor 89756f65b4 feat(ECR-012–016): 合规、题库、时辰刷新、头像、MBTI OEJTS 与埋点
落地输入合规、探索题库、报告日/时辰刷新、账号头像、OEJTS 量表,并补齐 H5 埋点与 Admin 漏斗;同步 ESS 工件、切至自建 Git、清理 GitHub Actions。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 01:27:58 +08:00

227 lines
5.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>
<PageShell>
<template #hero>
<div v-if="!report" class="head">
<HomeToolIcon name="portrait" :size="48" />
<div>
<h1 class="title">愈心解码</h1>
<p class="sub">一个生日读懂身与心</p>
</div>
</div>
<div v-else class="head compact">
<HomeToolIcon name="portrait" :size="40" />
<div>
<h1 class="title sm">愈心解码</h1>
<p class="sub">一个生日读懂身与心</p>
</div>
</div>
</template>
<div class="body" :class="{ 'has-sticky': !!report }">
<template v-if="needBirth && !report">
<div class="decode-cta">
<div class="decode-inner">
<HomeToolIcon name="portrait" :size="56" />
<h2 class="decode-title">愈心解码</h2>
<p class="decode-sub">一个生日读懂性格与身心节奏</p>
<div class="decode-inputs">
<BirthDateInputs v-model:year="year" v-model:month="month" v-model:day="day" />
<button class="decode-btn" type="button" :disabled="loading" @click="start">
{{ loading ? '解码中…' : '开始解码' }}
</button>
</div>
<p v-if="formError" class="yxg-err">{{ formError }}</p>
</div>
</div>
</template>
<p v-else-if="loading" class="yxg-hint">正在解码</p>
<p v-else-if="error && !report" class="yxg-err">
{{ error }}
<button type="button" class="yxg-link" @click="retry">重试</button>
</p>
<template v-else-if="report">
<DecodePanel
:summary="summary"
:detail="detail"
:deep="!!report.has_deep_access"
:paying="paying"
@unlock="buyDeep"
/>
<p v-if="error" class="yxg-err">{{ error }}</p>
<router-link v-if="report.id" class="yxg-link-plain report-link" :to="`/reports/${report.id}`">
在报告页打开
</router-link>
</template>
<p class="yxg-disc">自我探索与生活方式参考不是占卜或算命体质内容非医疗建议</p>
</div>
<div v-if="report" class="sticky-bar">
<router-link class="sticky-btn" to="/ask">去问答</router-link>
<router-link class="sticky-btn primary" to="/relation">去做匹配</router-link>
<button type="button" class="sticky-btn ghost" @click="shareOpen = true">分享</button>
</div>
<ShareSheet :open="shareOpen" :payload="sharePayload" @close="shareOpen = false" />
</PageShell>
</template>
<script setup lang="ts">
import BirthDateInputs from '../components/BirthDateInputs.vue'
import DecodePanel from '../components/DecodePanel.vue'
import HomeToolIcon from '../components/HomeToolIcon.vue'
import PageShell from '../components/PageShell.vue'
import ShareSheet from '../components/ShareSheet.vue'
import { usePortraitPage } from '../composables/usePortraitPage'
const {
loading,
needBirth,
error,
formError,
report,
paying,
shareOpen,
year,
month,
day,
summary,
detail,
sharePayload,
start,
retry,
buyDeep,
} = usePortraitPage()
</script>
<style scoped>
.head {
display: flex;
align-items: center;
gap: 12px;
margin-top: 8px;
padding-bottom: 8px;
}
.head.compact {
padding-bottom: 4px;
}
.title {
font-family: var(--font-display);
font-size: 22px;
font-weight: 700;
letter-spacing: 0.06em;
}
.title.sm {
font-size: 18px;
}
.sub {
font-size: 12px;
color: rgba(0, 0, 0, 0.38);
margin-top: 2px;
}
.body {
padding: 8px 16px 0;
}
.body.has-sticky {
padding-bottom: 72px;
}
.decode-cta {
margin: 8px 0 16px;
padding: 28px 20px 24px;
background: var(--color-surface);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-hero);
text-align: center;
}
.decode-inner {
display: flex;
flex-direction: column;
align-items: center;
}
.decode-title {
font-family: var(--font-display);
font-size: 20px;
font-weight: 700;
color: var(--color-accent-gold);
letter-spacing: 0.08em;
margin: 14px 0 4px;
}
.decode-sub {
font-size: 13px;
color: var(--color-text-tertiary);
margin: 0 0 18px;
}
.decode-inputs {
width: 100%;
display: flex;
flex-direction: column;
gap: 14px;
align-items: center;
}
.decode-inputs :deep(.bd) {
justify-content: center;
}
.decode-btn {
width: 100%;
max-width: 280px;
padding: 14px 24px;
border: none;
border-radius: 24px;
color: #fff;
font-size: 16px;
font-weight: 700;
background: linear-gradient(135deg, #ff7a6e, var(--color-primary));
box-shadow: 0 6px 18px rgba(229, 77, 66, 0.28);
cursor: pointer;
}
.decode-btn:disabled {
opacity: 0.65;
}
.report-link {
display: block;
margin: 12px 0;
}
.sticky-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
display: flex;
gap: 8px;
padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0));
background: rgba(255, 255, 255, 0.94);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-top: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.06);
}
.sticky-btn {
flex: 1;
padding: 12px 8px;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
text-align: center;
text-decoration: none;
border: 1.5px solid #eee;
background: #fff;
color: var(--color-text-primary);
cursor: pointer;
}
.sticky-btn.primary {
background: linear-gradient(135deg, #ff7a6e, var(--color-primary));
color: #fff;
border-color: transparent;
box-shadow: 0 4px 12px rgba(229, 77, 66, 0.22);
}
.sticky-btn.ghost {
flex: 0.8;
}
</style>