Files
digital-psychology/apps/user-h5/src/pages/MinePage.vue
T
jackyu66gitandCursor 7ab9add5dd
ci / h5 (push) Canceled after 0s
ci / api (push) Canceled after 0s
ci / ess-docs (push) Canceled after 0s
feat(ops): ECR-007 行为分析与 ECR-008 内容运营后台
落地埋点 ingest/数据看板、首页宫格 CMS 与测评上下架;含账号引导、问答流式与免责声明去重,以及 review P1 审计同事务修复。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 02:26:16 +08:00

262 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>
<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">{{ accountLabel }}</p>
<p class="meta">档案 {{ profileCount }} </p>
</div>
<router-link v-if="!loggedIn" class="edit" to="/login">登录 </router-link>
<button v-else type="button" class="edit linkish" @click="logout">退出</button>
</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 { useRouter } from 'vue-router'
import type { AuthUser, MembershipMe } from '@yuxingu/types'
import { api } from '../api/client'
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'
import { clearAuthToken } from '../lib/authSession'
const router = useRouter()
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)
const error = ref('')
const membership = ref<MembershipMe | null>(null)
const profileCount = ref(0)
const me = ref<AuthUser | null>(null)
const loggedIn = computed(() => !!me.value)
const accountLabel = computed(() => me.value?.nickname || me.value?.phone || '未登录')
const planLabel = computed(() => {
const p = membership.value?.plan
if (p === 'quarter') return '季卡'
if (p === 'year') return '年卡'
if (p === 'month') return '月卡'
return '会员中'
})
async function load() {
loading.value = true
error.value = ''
try {
me.value = await api.authMe()
const [m, profiles] = await Promise.all([api.getMembership(), api.listProfiles()])
membership.value = m
profileCount.value = (profiles.items || []).length
} catch (e) {
me.value = null
membership.value = null
profileCount.value = 0
error.value = e instanceof Error ? e.message : '请先登录'
} finally {
loading.value = false
}
}
async function logout() {
try {
await api.authLogout()
} catch {
/* ignore */
}
clearAuthToken()
me.value = null
await router.push('/login')
}
onMounted(load)
</script>
<style scoped>
.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;
}
.linkish {
border: 0;
background: none;
padding: 0;
cursor: pointer;
}
.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>