原先顶部渐变区单独一块「大头像 + 姓名 + 学校 + 学院专业标签」, 对用户实际没什么用,还占掉一屏顶部空间。 改动: - 删除顶部个人信息块(含学校名与学院/专业/班级标签) - 头像缩为 44px 圆形,移到会员卡内 Crown 图标的位置(该图标去掉), 姓名与会员身份(VIP/普通用户)合并为同一行 - VIP 角标改为贴在头像右下角 - 顶部渐变条高度压缩:padding-top 28px→14px、pb-20→pb-16, 会员卡仍以 -mt-14 上浮叠压,视觉层次不变 顺带清理不再使用的导入与计算属性:Crown 图标、GraduationCap 图标、 profileTags(学院/专业/班级标签)。 修掉 img 的 :src 缺可选链导致的 null 类型报错。 npm run build 与 vue-tsc 均通过。
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { LogOut, RefreshCw, Gift, X, Dices, TriangleAlert, SunMoon, Sun, Moon, Crown, Copy, ChevronRight, Smartphone, Coins, UserPlus, GraduationCap, Headset, ShoppingCart, Flame } from 'lucide-vue-next'
|
import { LogOut, RefreshCw, Gift, X, Dices, TriangleAlert, SunMoon, Sun, Moon, Copy, ChevronRight, Smartphone, Coins, UserPlus, Headset, ShoppingCart, Flame } from 'lucide-vue-next'
|
||||||
import { themeMode, setThemeMode, type ThemeMode } from '@/composables/useTheme'
|
import { themeMode, setThemeMode, type ThemeMode } from '@/composables/useTheme'
|
||||||
import { confirmDialog } from '@/composables/useConfirm'
|
import { confirmDialog } from '@/composables/useConfirm'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
@@ -163,13 +163,6 @@ function maskDeviceCode(code: string): string {
|
|||||||
// 是否已绑定设备码(决定「立即绑定 / 重新绑定」按钮文案)
|
// 是否已绑定设备码(决定「立即绑定 / 重新绑定」按钮文案)
|
||||||
const hasDeviceCode = computed(() => !!userInfo.value?.mobileCode)
|
const hasDeviceCode = computed(() => !!userInfo.value?.mobileCode)
|
||||||
|
|
||||||
// 头部信息标签:学院 / 专业 / 班级(有值才显示,最多 2 个,避免挤满)
|
|
||||||
const profileTags = computed(() => {
|
|
||||||
const u = userInfo.value
|
|
||||||
if (!u) return []
|
|
||||||
return [u.academy, u.className || u.major].filter((t): t is string => !!t && !!t.trim()).slice(0, 2)
|
|
||||||
})
|
|
||||||
|
|
||||||
// VIP 剩余天数(到期时间无效 / 已过期返回 null)
|
// VIP 剩余天数(到期时间无效 / 已过期返回 null)
|
||||||
const vipDaysLeft = computed(() => {
|
const vipDaysLeft = computed(() => {
|
||||||
if (!isVip.value || !vipExpireAt.value) return null
|
if (!isVip.value || !vipExpireAt.value) return null
|
||||||
@@ -464,32 +457,12 @@ async function handleRedeemInvite() {
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col min-h-full">
|
<div class="flex flex-col min-h-full">
|
||||||
<!-- ======== 顶部:品牌渐变背景 + 个人信息(左对齐)+ 会员卡 ======== -->
|
<!-- ======== 顶部:品牌渐变背景 + 个人信息(左对齐)+ 会员卡 ======== -->
|
||||||
<div class="relative overflow-hidden hero-clip bg-brand-gradient px-5 pb-20 text-white flex-shrink-0" style="padding-top: calc(var(--safe-top) + 28px)">
|
<div class="relative overflow-hidden hero-clip bg-brand-gradient px-5 pb-16 text-white flex-shrink-0" style="padding-top: calc(var(--safe-top) + 14px)">
|
||||||
<!-- 装饰:柔光圆 + 细网格纹理 -->
|
<!-- 装饰:柔光圆 + 细网格纹理 -->
|
||||||
<div class="pointer-events-none absolute -right-16 -top-20 w-64 h-64 rounded-full bg-white/10" />
|
<div class="pointer-events-none absolute -right-16 -top-20 w-64 h-64 rounded-full bg-white/10" />
|
||||||
<div class="pointer-events-none absolute right-10 top-24 w-28 h-28 rounded-full bg-white/10" />
|
<div class="pointer-events-none absolute right-10 top-24 w-28 h-28 rounded-full bg-white/10" />
|
||||||
<div class="pointer-events-none absolute inset-0 profile-hero-grid" />
|
<div class="pointer-events-none absolute inset-0 profile-hero-grid" />
|
||||||
|
|
||||||
<!-- 头像 + 姓名 + 学校 -->
|
|
||||||
<div class="relative flex items-center gap-4 rise-in">
|
|
||||||
<div class="relative flex-shrink-0">
|
|
||||||
<div class="w-[68px] h-[68px] rounded-full overflow-hidden ring-[3px] ring-white/60 shadow-lg shadow-black/15 bg-white/20 flex items-center justify-center">
|
|
||||||
<img v-show="userInfo?.avatar && !avatarFailed" :src="userInfo?.avatar" class="w-full h-full object-cover" alt="头像" @error="avatarFailed = true" />
|
|
||||||
<span v-if="!userInfo?.avatar || avatarFailed" class="text-2xl font-bold">{{ userInfo?.realName?.[0] || userInfo?.username?.[0] || '?' }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="isVip" class="vip-badge absolute -right-2 -bottom-1 z-10 whitespace-nowrap">VIP</div>
|
|
||||||
</div>
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<p class="text-[22px] font-bold tracking-tight leading-tight truncate">{{ userInfo?.realName || userInfo?.username || '--' }}</p>
|
|
||||||
<p class="mt-1 flex items-center gap-1 text-sm text-white/90 truncate">
|
|
||||||
<GraduationCap class="w-3.5 h-3.5 flex-shrink-0" />
|
|
||||||
<span class="truncate">{{ userInfo?.schoolName || '--' }}</span>
|
|
||||||
</p>
|
|
||||||
<div v-if="profileTags.length" class="mt-2 flex flex-wrap gap-1.5">
|
|
||||||
<span v-for="t in profileTags" :key="t" class="max-w-[10rem] truncate text-xs rounded-md bg-white/15 ring-1 ring-white/20 px-1.5 py-0.5 text-white/95">{{ t }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ======== 会员卡(压在头图下沿) ======== -->
|
<!-- ======== 会员卡(压在头图下沿) ======== -->
|
||||||
@@ -497,14 +470,20 @@ async function handleRedeemInvite() {
|
|||||||
<div class="member-card relative overflow-hidden hero-clip rounded-3xl p-4 shadow-xl" :class="isVip ? 'is-vip' : 'is-free'">
|
<div class="member-card relative overflow-hidden hero-clip rounded-3xl p-4 shadow-xl" :class="isVip ? 'is-vip' : 'is-free'">
|
||||||
<div class="pointer-events-none absolute -right-8 -top-10 w-36 h-36 rounded-full member-card-glow" />
|
<div class="pointer-events-none absolute -right-8 -top-10 w-36 h-36 rounded-full member-card-glow" />
|
||||||
<div class="relative flex items-center gap-3">
|
<div class="relative flex items-center gap-3">
|
||||||
<div class="member-icon w-11 h-11 rounded-2xl flex items-center justify-center flex-shrink-0">
|
<!-- 头像(原顶部头图内容整合至此处) -->
|
||||||
<Crown class="w-5 h-5" :stroke-width="2.4" />
|
<div class="relative flex-shrink-0">
|
||||||
|
<div class="w-11 h-11 rounded-full overflow-hidden ring-2 ring-white/70 shadow-sm bg-white/30 flex items-center justify-center">
|
||||||
|
<img v-show="userInfo?.avatar && !avatarFailed" :src="userInfo?.avatar" class="w-full h-full object-cover" alt="头像" @error="avatarFailed = true" />
|
||||||
|
<span v-if="!userInfo?.avatar || avatarFailed" class="text-base font-bold" :class="isVip ? 'text-amber-800' : 'text-brand-700'">{{ userInfo?.realName?.[0] || userInfo?.username?.[0] || '?' }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="isVip" class="vip-badge absolute -right-1.5 -bottom-1 z-10 whitespace-nowrap">VIP</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="member-title text-base font-bold tracking-tight">{{ isVip ? 'VIP 会员' : '普通用户' }}</p>
|
<!-- 姓名 + 会员身份 -->
|
||||||
|
<p class="member-title text-base font-bold tracking-tight truncate">{{ userInfo?.realName || userInfo?.username || '--' }}</p>
|
||||||
<p class="member-sub text-xs mt-0.5 truncate">
|
<p class="member-sub text-xs mt-0.5 truncate">
|
||||||
<template v-if="isVip">{{ vipExpireShort ? `有效期至 ${vipExpireShort}` : '会员生效中' }}<template v-if="vipDaysLeft !== null"> · 剩 {{ vipDaysLeft }} 天</template></template>
|
<template v-if="isVip">{{ vipExpireShort ? `VIP · 有效期至 ${vipExpireShort}` : 'VIP 会员生效中' }}<template v-if="vipDaysLeft !== null"> · 剩 {{ vipDaysLeft }} 天</template></template>
|
||||||
<template v-else>开通后执行任务不限次数</template>
|
<template v-else>普通用户 · 开通后执行任务不限次数</template>
|
||||||
</p>
|
</p>
|
||||||
<!-- 可执行次数:普通用户与 VIP 都显示(VIP 为不限次数时显示「不限」) -->
|
<!-- 可执行次数:普通用户与 VIP 都显示(VIP 为不限次数时显示「不限」) -->
|
||||||
<p class="member-sub text-xs mt-1 flex items-center gap-1">
|
<p class="member-sub text-xs mt-1 flex items-center gap-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user