feat: 按测测功能重构 H5 首页与各子页,并修正顶栏「+」添加档案菜单
对齐测测交互:首页「+」支持邀请填档案/添加档案/邀请合盘;各 Tab 与工具页按同一视觉与功能标准落地;本地对照截图目录显式忽略。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,61 +1,97 @@
|
||||
<template>
|
||||
<main class="yxg-page">
|
||||
<div class="yxg-hero">
|
||||
<PageTitle feature="profile" title="我的" sub="个人档案 · 成长报告 · 成长会员" />
|
||||
</div>
|
||||
|
||||
<div class="yxg-sheet">
|
||||
<p v-if="loading" class="yxg-hint pad">加载中…</p>
|
||||
<p v-else-if="error" class="yxg-err pad">
|
||||
{{ error }}
|
||||
<button type="button" class="yxg-link" @click="load">重试</button>
|
||||
</p>
|
||||
<template v-else>
|
||||
<div class="yxg-card member">
|
||||
<p class="yxg-chip-ico">
|
||||
<span class="yxg-mark icon-purple tiny" aria-hidden="true">⑨</span>
|
||||
<span class="member-text">
|
||||
<strong v-if="membership?.active">成长会员有效 · {{ planLabel }}</strong>
|
||||
<strong v-else>尚未开通成长会员</strong>
|
||||
<span class="yxg-meta">档案 {{ profileCount }} 份</span>
|
||||
</span>
|
||||
</p>
|
||||
<router-link class="yxg-btn" to="/membership">
|
||||
{{ membership?.active ? '查看会员' : '开通成长会员' }}
|
||||
</router-link>
|
||||
<PageShell>
|
||||
<template #hero>
|
||||
<div class="profile-head">
|
||||
<span class="av" aria-hidden="true">
|
||||
<BrandLogo size="card" class="av-logo" />
|
||||
</span>
|
||||
<div class="who">
|
||||
<p class="name">愈心谷用户</p>
|
||||
<p class="meta">档案 {{ profileCount }} 份</p>
|
||||
</div>
|
||||
<ul class="yxg-list">
|
||||
<li v-for="item in links" :key="item.to">
|
||||
<router-link :to="item.to">
|
||||
<span class="yxg-mark" :class="'icon-' + item.tone" aria-hidden="true">{{ item.icon }}</span>
|
||||
<span class="yxg-body"><strong>{{ item.label }}</strong></span>
|
||||
<span class="chev" aria-hidden="true">›</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
<router-link class="edit" to="/profile">编辑 ›</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p v-if="loading" class="hint">加载中…</p>
|
||||
<p v-else-if="error" class="err">
|
||||
{{ error }}
|
||||
<button type="button" class="retry" @click="load">重试</button>
|
||||
</p>
|
||||
<template v-else>
|
||||
<!-- 资产条 -->
|
||||
<section class="assets">
|
||||
<router-link to="/membership" class="asset">
|
||||
<strong>{{ membership?.active ? planLabel : '未开通' }}</strong>
|
||||
<span>成长会员</span>
|
||||
</router-link>
|
||||
<router-link to="/ask" class="asset">
|
||||
<strong>{{ profileCount }}</strong>
|
||||
<span>档案</span>
|
||||
</router-link>
|
||||
<router-link to="/reports" class="asset">
|
||||
<strong>报告</strong>
|
||||
<span>成长记录</span>
|
||||
</router-link>
|
||||
<router-link to="/membership" class="asset accent">
|
||||
<strong>{{ membership?.active ? '续费' : '开通' }}</strong>
|
||||
<span>会员中心</span>
|
||||
</router-link>
|
||||
</section>
|
||||
|
||||
<section class="sec">
|
||||
<p class="sec-title">档案与内容</p>
|
||||
<div class="group">
|
||||
<ListRow
|
||||
v-for="item in groupArchive"
|
||||
:key="item.to"
|
||||
:to="item.to"
|
||||
:title="item.label"
|
||||
:icon="item.icon"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sec">
|
||||
<p class="sec-title">成长与会员</p>
|
||||
<div class="group">
|
||||
<ListRow
|
||||
v-for="item in groupGrowth"
|
||||
:key="item.to"
|
||||
:to="item.to"
|
||||
:title="item.label"
|
||||
:icon="item.icon"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</PageShell>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import type { MembershipMe } from '@yuxingu/types'
|
||||
import { api } from '../api/client'
|
||||
import PageTitle from '../components/PageTitle.vue'
|
||||
import type { IconTone } from '../lib/featureIcons'
|
||||
import BrandLogo from '../components/BrandLogo.vue'
|
||||
import ListRow from '../components/ListRow.vue'
|
||||
import PageShell from '../components/PageShell.vue'
|
||||
import type { HomeToolIconName } from '../components/HomeToolIcon.vue'
|
||||
|
||||
const links: { to: string; icon: string; label: string; tone: IconTone }[] = [
|
||||
{ to: '/profile', icon: '◍', label: '个人档案', tone: 'mute' },
|
||||
{ to: '/reports', icon: '▣', label: '我的成长报告', tone: 'orange' },
|
||||
{ to: '/', icon: '△', label: '个人画像', tone: 'gold' },
|
||||
{ to: '/star', icon: '✦', label: '星象性格', tone: 'night' },
|
||||
{ to: '/rhythm', icon: '☯', label: '身心节律', tone: 'green' },
|
||||
{ to: '/cards', icon: '◈', label: '意象卡片', tone: 'teal' },
|
||||
{ to: '/relation', icon: '♡', label: '关系理解', tone: 'rose' },
|
||||
{ to: '/ask', icon: '◎', label: 'AI 成长助手', tone: 'gold' },
|
||||
{ to: '/explore', icon: '◆', label: '探索测试', tone: 'blue' },
|
||||
{ to: '/membership', icon: '⑨', label: '成长会员', tone: 'purple' },
|
||||
const groupArchive: { to: string; label: string; icon: HomeToolIconName }[] = [
|
||||
{ to: '/profile', label: '个人档案', icon: 'portrait' },
|
||||
{ to: '/reports', label: '我的成长报告', icon: 'reports' },
|
||||
{ to: '/portrait', label: '愈心解码', icon: 'portrait' },
|
||||
{ to: '/star', label: '星象性格', icon: 'star' },
|
||||
{ to: '/rhythm', label: '身心节律', icon: 'rhythm' },
|
||||
{ to: '/cards', label: '意象卡片', icon: 'cards' },
|
||||
]
|
||||
|
||||
const groupGrowth: { to: string; label: string; icon: HomeToolIconName }[] = [
|
||||
{ to: '/relation', label: '人格匹配', icon: 'relation' },
|
||||
{ to: '/ask', label: 'AI 成长助手', icon: 'ask' },
|
||||
{ to: '/explore', label: '探索测试', icon: 'mbti' },
|
||||
{ to: '/growth-plan', label: '成长计划', icon: 'growth' },
|
||||
{ to: '/membership', label: '成长会员', icon: 'growth' },
|
||||
]
|
||||
|
||||
const loading = ref(true)
|
||||
@@ -68,7 +104,7 @@ const planLabel = computed(() => {
|
||||
if (p === 'quarter') return '季卡'
|
||||
if (p === 'year') return '年卡'
|
||||
if (p === 'month') return '月卡'
|
||||
return '成长会员'
|
||||
return '会员中'
|
||||
})
|
||||
|
||||
async function load() {
|
||||
@@ -89,10 +125,107 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pad{padding:8px 16px}
|
||||
.member{display:flex;flex-direction:column;gap:12px;align-items:flex-start}
|
||||
.member-text{display:flex;flex-direction:column;gap:2px}
|
||||
.member-text strong{font-size:15px;color:#222;font-weight:650}
|
||||
.tiny{width:36px!important;height:36px!important;font-size:16px!important;border-radius:12px!important}
|
||||
.yxg-btn{margin-top:2px}
|
||||
.profile-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 0 12px;
|
||||
}
|
||||
.av {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(145deg, #ffe8e0, #ffd0c4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.av-logo {
|
||||
width: 34px !important;
|
||||
height: auto !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
.who { flex: 1; min-width: 0; }
|
||||
.name {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.meta {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.edit {
|
||||
font-size: 12px;
|
||||
color: #bbb;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.assets {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 8px;
|
||||
padding: 0 16px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.asset {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 12px 4px;
|
||||
background: #fafafa;
|
||||
border-radius: var(--radius-md);
|
||||
text-align: center;
|
||||
}
|
||||
.asset strong {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
}
|
||||
.asset span {
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
}
|
||||
.asset.accent strong {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
.sec {
|
||||
padding: 0 16px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.sec-title {
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.group {
|
||||
background: #fafafa;
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 2px 10px;
|
||||
}
|
||||
.group :deep(.lr) {
|
||||
border-bottom: 1px solid #f0ebe8;
|
||||
}
|
||||
.group :deep(.lr:last-child) {
|
||||
border-bottom: none;
|
||||
}
|
||||
.hint, .err {
|
||||
padding: 16px;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
.err { color: var(--color-primary); }
|
||||
.retry {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--color-accent-blue);
|
||||
text-decoration: underline;
|
||||
margin-left: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user