281 lines
7.0 KiB
Vue
281 lines
7.0 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* 首页会员卡片。
|
|
*
|
|
* 与工具页的悬浮广告是同一套视觉语言(深炭底 + 金边 + 呼吸光),
|
|
* 区别是它承担「整体介绍」职责:会员状态、额度三池概览、两个转化入口。
|
|
*
|
|
* 会员且额度充足时不再「催买」,改为展示额度状态 + 续费入口,避免打扰付费用户。
|
|
*/
|
|
import { computed, onMounted, ref } from 'vue'
|
|
import { useQuotaStore } from '@/stores/quota'
|
|
|
|
const store = useQuotaStore()
|
|
const ready = ref(false)
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
await store.fetchMine()
|
|
ready.value = true
|
|
}
|
|
catch {
|
|
// 未登录 / 网络异常:退化为通用文案,不阻塞首页
|
|
}
|
|
})
|
|
|
|
/** 三池合计(跨工具求和,首页只看总量) */
|
|
const agg = computed(() => {
|
|
const list = store.mine?.tools || []
|
|
return {
|
|
free: list.reduce((sum, t) => sum + t.free_left, 0),
|
|
member: list.reduce((sum, t) => sum + t.member_left, 0),
|
|
paid: list.reduce((sum, t) => sum + t.paid_left, 0),
|
|
}
|
|
})
|
|
|
|
const isMember = computed(() => !!store.mine?.is_member)
|
|
const levelName = computed(() => store.mine?.level_name || '')
|
|
const totalLeft = computed(() => store.mine?.total_left ?? 0)
|
|
const periodDays = computed(() => store.periodDays)
|
|
|
|
const title = computed(() => (isMember.value ? `${levelName.value}会员` : '开通会员 · 解锁全部工具'))
|
|
const subline = computed(() => {
|
|
if (isMember.value) {
|
|
const expire = store.mine?.level_expire_at
|
|
return expire ? `${expire} 到期 · 额度每 ${periodDays.value} 天重置` : `额度每 ${periodDays.value} 天重置`
|
|
}
|
|
return `免费额度每 ${periodDays.value} 天重置,会员再送 10 倍额度`
|
|
})
|
|
|
|
const pools = computed(() => [
|
|
{ key: 'free', label: '免费', value: ready.value ? String(agg.value.free) : '—' },
|
|
{ key: 'member', label: '会员', value: isMember.value ? String(agg.value.member) : '未开通' },
|
|
{ key: 'paid', label: '次数包', value: ready.value ? String(agg.value.paid) : '—' },
|
|
])
|
|
|
|
function goMember() {
|
|
uni.navigateTo({ url: '/pkg-member/member/index' })
|
|
}
|
|
|
|
function goPacks() {
|
|
uni.navigateTo({ url: '/pkg-member/quota/index' })
|
|
}
|
|
|
|
function goMyQuota() {
|
|
uni.navigateTo({ url: '/pkg-member/quota/my' })
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<view class="vcard">
|
|
<view class="vcard__aura" />
|
|
<view class="vcard__sweep" />
|
|
|
|
<view class="vcard__top">
|
|
<view class="vcard__crown">
|
|
<text class="vcard__crown-text">
|
|
VIP
|
|
</text>
|
|
</view>
|
|
<view class="vcard__meta">
|
|
<text class="vcard__title">
|
|
{{ title }}
|
|
</text>
|
|
<text class="vcard__sub">
|
|
{{ subline }}
|
|
</text>
|
|
</view>
|
|
<view class="vcard__mine press" hover-class="press-active" @tap.stop="goMyQuota">
|
|
<text>我的额度</text>
|
|
<wd-icon name="arrow-right" size="22rpx" color="rgba(255,233,184,0.75)" />
|
|
</view>
|
|
</view>
|
|
|
|
<view class="vcard__pools">
|
|
<view v-for="p in pools" :key="p.key" class="vpool">
|
|
<text class="vpool__val">
|
|
{{ p.value }}
|
|
</text>
|
|
<text class="vpool__label">
|
|
{{ p.label }}
|
|
</text>
|
|
</view>
|
|
<view class="vpool vpool--total">
|
|
<text class="vpool__val vpool__val--gold">
|
|
{{ ready ? totalLeft : '—' }}
|
|
</text>
|
|
<text class="vpool__label">
|
|
本周期可用
|
|
</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="vcard__acts">
|
|
<view class="vbtn vbtn--gold press" hover-class="press-active" @tap.stop="goMember">
|
|
<text>{{ isMember ? '续费会员' : '立即开通会员' }}</text>
|
|
</view>
|
|
<view class="vbtn vbtn--ghost press" hover-class="press-active" @tap.stop="goPacks">
|
|
<text>买次数包</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.vcard {
|
|
position: relative;
|
|
overflow: hidden;
|
|
padding: 28rpx 26rpx 26rpx;
|
|
border-radius: 34rpx;
|
|
background: linear-gradient(160deg, #2b1a10 0%, #3d2413 52%, #22130a 100%);
|
|
border: 2rpx solid rgba(245, 197, 107, 0.55);
|
|
box-shadow: 0 18rpx 42rpx rgba(43, 26, 16, 0.34), 0 0 0 6rpx rgba(245, 197, 107, 0.08);
|
|
}
|
|
.vcard__aura {
|
|
position: absolute;
|
|
top: -120rpx;
|
|
right: -80rpx;
|
|
width: 340rpx;
|
|
height: 340rpx;
|
|
border-radius: 50%;
|
|
background: radial-gradient(circle, rgba(245, 197, 107, 0.3) 0%, rgba(245, 197, 107, 0) 70%);
|
|
}
|
|
.vcard__sweep {
|
|
position: absolute;
|
|
top: 0;
|
|
left: -50%;
|
|
width: 36%;
|
|
height: 100%;
|
|
background: linear-gradient(100deg, rgba(255, 255, 255, 0) 0%, rgba(255, 226, 168, 0.16) 50%, rgba(255, 255, 255, 0) 100%);
|
|
animation: vcardSweep 4.6s ease-in-out infinite;
|
|
}
|
|
|
|
.vcard__top {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20rpx;
|
|
}
|
|
.vcard__crown {
|
|
flex-shrink: 0;
|
|
width: 80rpx;
|
|
height: 80rpx;
|
|
border-radius: 24rpx;
|
|
background: linear-gradient(140deg, #ffe9b8 0%, #f5c56b 50%, #d89a35 100%);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 10rpx 24rpx rgba(216, 154, 53, 0.42);
|
|
}
|
|
.vcard__crown-text {
|
|
font-size: 26rpx;
|
|
font-weight: 900;
|
|
letter-spacing: 0.06em;
|
|
color: #3d2413;
|
|
}
|
|
.vcard__meta {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
.vcard__title {
|
|
display: block;
|
|
font-size: 32rpx;
|
|
font-weight: 800;
|
|
color: #ffe9b8;
|
|
letter-spacing: 0.01em;
|
|
}
|
|
.vcard__sub {
|
|
display: block;
|
|
margin-top: 8rpx;
|
|
font-size: 21rpx;
|
|
color: rgba(255, 233, 184, 0.62);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.vcard__mine {
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 2rpx;
|
|
padding: 8rpx 16rpx;
|
|
border-radius: 999rpx;
|
|
border: 2rpx solid rgba(255, 233, 184, 0.24);
|
|
font-size: 21rpx;
|
|
color: rgba(255, 233, 184, 0.78);
|
|
}
|
|
|
|
.vcard__pools {
|
|
position: relative;
|
|
display: flex;
|
|
gap: 12rpx;
|
|
margin-top: 24rpx;
|
|
}
|
|
.vpool {
|
|
flex: 1;
|
|
min-width: 0;
|
|
padding: 16rpx 8rpx;
|
|
border-radius: 22rpx;
|
|
background: rgba(245, 197, 107, 0.09);
|
|
border: 2rpx solid rgba(245, 197, 107, 0.16);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 4rpx;
|
|
}
|
|
.vpool--total {
|
|
background: rgba(245, 197, 107, 0.16);
|
|
border-color: rgba(255, 233, 184, 0.3);
|
|
}
|
|
.vpool__val {
|
|
font-size: 30rpx;
|
|
font-weight: 800;
|
|
color: #ffe9b8;
|
|
line-height: 1.1;
|
|
}
|
|
.vpool__val--gold {
|
|
background: linear-gradient(180deg, #fff6e0 0%, #f5c56b 100%);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
color: transparent;
|
|
}
|
|
.vpool__label {
|
|
font-size: 19rpx;
|
|
color: rgba(255, 233, 184, 0.55);
|
|
}
|
|
|
|
.vcard__acts {
|
|
position: relative;
|
|
display: flex;
|
|
gap: 14rpx;
|
|
margin-top: 24rpx;
|
|
}
|
|
.vbtn {
|
|
flex: 1;
|
|
text-align: center;
|
|
padding: 20rpx 0;
|
|
border-radius: 999rpx;
|
|
font-size: 26rpx;
|
|
font-weight: 700;
|
|
}
|
|
.vbtn--gold {
|
|
color: #3d2413;
|
|
background: linear-gradient(135deg, #ffe9b8 0%, #f5c56b 55%, #e0a63f 100%);
|
|
box-shadow: 0 12rpx 26rpx rgba(224, 166, 63, 0.34);
|
|
}
|
|
.vbtn--ghost {
|
|
color: #ffe9b8;
|
|
border: 2rpx solid rgba(245, 197, 107, 0.5);
|
|
background: rgba(255, 255, 255, 0.04);
|
|
}
|
|
|
|
@keyframes vcardSweep {
|
|
0% {
|
|
left: -50%;
|
|
}
|
|
55%, 100% {
|
|
left: 140%;
|
|
}
|
|
}
|
|
</style>
|