落地输入合规、探索题库、报告日/时辰刷新、账号头像、OEJTS 量表,并补齐 H5 埋点与 Admin 漏斗;同步 ESS 工件、切至自建 Git、清理 GitHub Actions。 Co-authored-by: Cursor <cursoragent@cursor.com>
227 lines
5.5 KiB
Vue
227 lines
5.5 KiB
Vue
<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>
|