feat: 按测测功能重构 H5 首页与各子页,并修正顶栏「+」添加档案菜单

对齐测测交互:首页「+」支持邀请填档案/添加档案/邀请合盘;各 Tab 与工具页按同一视觉与功能标准落地;本地对照截图目录显式忽略。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-05 14:32:35 +08:00
co-authored by Cursor
parent bd22d9dddd
commit 53eb4577b3
35 changed files with 8900 additions and 1356 deletions
+121 -22
View File
@@ -1,30 +1,52 @@
<template>
<main class="yxg-page">
<div class="yxg-hero">
<PageShell>
<template #hero>
<BackButton />
<PageTitle feature="membership" title="成长会员" sub="完整分析 · AI成长助手更多次数 · 专属内容" />
</div>
<div class="yxg-sheet sheet-pad">
<p v-if="loading" class="yxg-hint">加载中</p>
<p v-else-if="error" class="yxg-err">
<div class="head">
<HomeToolIcon name="growth" :size="48" />
<div>
<h1 class="title">成长会员</h1>
<p class="sub">完整分析 · AI 更多次数 · 专属内容</p>
</div>
</div>
</template>
<div class="body">
<p v-if="loading" class="hint">加载中</p>
<p v-else-if="error" class="err">
{{ error }}
<button type="button" class="yxg-link" @click="load">重试</button>
<button type="button" class="retry" @click="load">重试</button>
</p>
<template v-else>
<div v-if="me?.active" class="yxg-card ok">
<div v-if="me?.active" class="status-card on">
<p class="ok-title">会员有效 · {{ planLabel }}</p>
<p v-if="me.expires_at" class="yxg-meta">到期{{ formatDate(me.expires_at) }}</p>
<p class="yxg-meta">问答额度剩余{{ me.ask_quota_left ?? 0 }}</p>
<p v-if="me.expires_at" class="meta">到期{{ formatDate(me.expires_at) }}</p>
<p class="meta">问答额度剩余{{ me.ask_quota_left ?? 0 }}</p>
</div>
<div v-else class="yxg-card">
<p>尚未开通成长会员开通后可查看画像与关系理解的完整分析</p>
<button class="yxg-btn" type="button" :disabled="paying" @click="subscribe('month')">开通月卡模拟支付</button>
<button class="yxg-btn yxg-btn-ghost" type="button" :disabled="paying" @click="subscribe('quarter')">季卡</button>
<button class="yxg-btn yxg-btn-ghost" type="button" :disabled="paying" @click="subscribe('year')">年卡</button>
<div v-else class="status-card">
<p class="lead">开通后可查看画像与关系理解的完整分析并获得更多 AI 问答次数</p>
</div>
<div class="plans">
<button
v-for="p in plans"
:key="p.key"
type="button"
class="plan"
:class="{ featured: p.featured }"
:disabled="paying"
@click="subscribe(p.key)"
>
<span v-if="p.tag" class="tag">{{ p.tag }}</span>
<strong>{{ p.label }}</strong>
<span class="price">{{ p.price }}</span>
<span class="desc">{{ p.desc }}</span>
</button>
</div>
<p class="note">当前为模拟支付便于本地验收</p>
</template>
</div>
</main>
</PageShell>
</template>
<script setup lang="ts">
@@ -32,9 +54,16 @@ import { computed, onMounted, ref } from 'vue'
import type { MembershipMe } from '@yuxingu/types'
import { api } from '../api/client'
import BackButton from '../components/BackButton.vue'
import PageTitle from '../components/PageTitle.vue'
import HomeToolIcon from '../components/HomeToolIcon.vue'
import PageShell from '../components/PageShell.vue'
import { AnalyticsEvent, track } from '../lib/analytics'
const plans = [
{ key: 'month', label: '月卡', price: '模拟开通', desc: '按月灵活体验', featured: false, tag: '' },
{ key: 'quarter', label: '季卡', price: '模拟开通', desc: '三个月持续成长', featured: true, tag: '推荐' },
{ key: 'year', label: '年卡', price: '模拟开通', desc: '全年陪伴与报告', featured: false, tag: '' },
]
const loading = ref(true)
const paying = ref(false)
const error = ref('')
@@ -87,8 +116,78 @@ onMounted(load)
</script>
<style scoped>
.yxg-card{margin-top:8px}
.ok{border:1px solid #c8e6c9;background:#f4fff4}
.ok-title{font-size:15px;font-weight:650;color:#222;margin-bottom:6px}
.yxg-btn{margin-top:12px;margin-right:8px}
.head {
display: flex;
align-items: center;
gap: 12px;
margin-top: 8px;
padding-bottom: 8px;
}
.title {
font-family: var(--font-display);
font-size: 22px;
font-weight: 700;
letter-spacing: 0.06em;
}
.sub {
font-size: 12px;
color: rgba(0, 0, 0, 0.38);
margin-top: 2px;
}
.body { padding: 8px 16px 0; }
.status-card {
padding: 16px;
border-radius: var(--radius-xl);
background: #fafafa;
margin-bottom: 14px;
}
.status-card.on {
background: linear-gradient(145deg, #fff8e8, #ffeec8);
box-shadow: var(--shadow-card);
}
.ok-title { font-size: 16px; font-weight: 700; color: #222; }
.meta { font-size: 12px; color: #888; margin-top: 4px; }
.lead { font-size: 14px; color: #555; line-height: 1.6; }
.plans { display: flex; flex-direction: column; gap: 10px; }
.plan {
position: relative;
text-align: left;
padding: 16px;
border-radius: var(--radius-lg);
border: 1.5px solid #f0ebe8;
background: #fff;
display: flex;
flex-direction: column;
gap: 4px;
}
.plan.featured {
border-color: #f5c4bc;
background: linear-gradient(145deg, #fff7f4, #ffe9e2);
box-shadow: var(--shadow-hero);
}
.plan strong { font-size: 16px; color: #222; }
.price { font-size: 13px; font-weight: 650; color: var(--color-primary); }
.desc { font-size: 12px; color: #999; }
.tag {
position: absolute;
top: 10px;
right: 10px;
font-size: 9px;
font-weight: 700;
color: #fff;
background: linear-gradient(135deg, #ff7a6e, var(--color-primary));
padding: 2px 6px;
border-radius: 6px;
}
.plan:disabled { opacity: 0.55; }
.note { margin-top: 12px; font-size: 11px; color: #ccc; text-align: center; }
.hint, .err { font-size: 13px; color: #999; padding: 8px 0; }
.err { color: var(--color-primary); }
.retry {
border: none;
background: transparent;
color: var(--color-accent-blue);
text-decoration: underline;
margin-left: 6px;
}
</style>