refactor(wallet):移除套餐功能,此功能有问题,暂不启用

This commit is contained in:
qian committed 2026-06-06 22:19:37 +08:00
1 parent ac5566b141
commit aa642c3628
1 file changed
+146 -518
+146 -518
View File
@@ -5,7 +5,6 @@ import Icon from '@/components/common/Icon.vue'
import request from '@/utils/request'
const route = useRoute()
const loading = ref(false)
const wallet = ref<any>(null)
// Recharge
@@ -19,14 +18,6 @@ const paymentInfo = ref<any>(null)
// Pay result notification
const payResult = ref<'success' | 'fail' | ''>('')
// Plans
const plans = ref<any[]>([])
const currentPlan = ref<any>(null)
const showConfirm = ref(false)
const selectedPlan = ref<any>(null)
const selectedMonths = ref(1)
const confirmLoading = ref(false)
// Frozen items
const showFrozenModal = ref(false)
const frozenItems = ref<any[]>([])
@@ -43,15 +34,6 @@ const redeemHistoryLoading = ref(false)
const redeemTypeLabels: Record<string, string> = { quota: '额度', plan: '套餐', duration: '时长' }
const redeemTypeBadgeClasses: Record<string, string> = { quota: 'badge-success', plan: 'badge-primary', duration: 'badge-warning' }
const monthsOptions = [
{ label: '1 个月', value: 1 },
{ label: '3 个月', value: 3 },
{ label: '6 个月', value: 6 },
{ label: '12 个月(优惠)', value: 12 },
]
const currentPlanId = computed(() => currentPlan.value?.plan_id)
// Computed: preset amounts from payment settings
const presetAmounts = computed(() => {
if (paymentInfo.value?.amount_options?.length) {
@@ -79,12 +61,6 @@ const payMethods = computed(() => {
return methods
})
// Current plan details (matched from plans list)
const currentPlanDetail = computed(() => {
if (!currentPlan.value || !plans.value.length) return null
return plans.value.find((p: any) => p.id === currentPlan.value.plan_id) || null
})
// Balance warning
const isLowBalance = computed(() => {
if (!wallet.value) return false
@@ -109,23 +85,6 @@ async function fetchPaymentInfo() {
}
}
async function fetchPlans() {
loading.value = true
try {
const [plansRes, currentRes] = await Promise.all([
request.get('/tenant/plans'),
request.get('/tenant/plan/current').catch(() => ({ data: { data: null } })),
])
const raw = plansRes.data?.data
plans.value = Array.isArray(raw) ? raw : (raw?.data || raw?.list || [])
currentPlan.value = currentRes.data?.data || null
} catch {
// interceptor handles error toast
} finally {
loading.value = false
}
}
function selectPresetAmount(amount: number) {
rechargeAmount.value = amount
customAmount.value = ''
@@ -164,42 +123,6 @@ async function handleRecharge() {
}
}
function calcPrice(plan: any, months: number) {
if (months >= 12) return Number(plan.yearly_price || 0)
return Number(plan.monthly_price || 0) * months
}
function openConfirm(plan: any) {
selectedPlan.value = plan
selectedMonths.value = 1
showConfirm.value = true
}
async function handleSubscribe() {
confirmLoading.value = true
try {
const res = await request.post('/tenant/orders/create', {
order_type: 'new_plan',
plan_id: selectedPlan.value.id,
months: selectedMonths.value,
})
const order = res.data?.data
if (order?.id) {
await request.post(`/tenant/orders/${order.id}/pay`)
}
showConfirm.value = false
await fetchPlans()
} catch {
// interceptor handles error toast
} finally {
confirmLoading.value = false
}
}
function isCurrentPlan(plan: any) {
return currentPlan.value && currentPlan.value.plan_id === plan.id
}
// Frozen items
async function fetchFrozenItems() {
frozenLoading.value = true
@@ -284,7 +207,6 @@ function formatRemaining(seconds: number): string {
onMounted(() => {
fetchWallet()
fetchPlans()
fetchPaymentInfo()
// Handle pay result from return URL
@@ -306,12 +228,12 @@ onBeforeUnmount(() => {
</script>
<template>
<div class="wallet-page space-y-8">
<div class="wallet-page space-y-6">
<!-- Page Header -->
<div class="page-header flex items-start justify-between">
<div>
<h1 class="page-title">钱包</h1>
<p class="page-description">管理余额、充值和套餐方案</p>
<p class="page-description">管理余额与充值</p>
</div>
<button class="btn btn-secondary btn-sm" @click="openRedeemModal">
<Icon name="gift" size="sm" />
@@ -348,408 +270,162 @@ onBeforeUnmount(() => {
</transition>
<!-- ============================================ -->
<!-- Hero Balance Card -->
<!-- Two-column: Balance + Recharge -->
<!-- ============================================ -->
<div class="balance-hero">
<!-- Background decorations -->
<div class="balance-hero-bg">
<div class="balance-hero-orb balance-hero-orb-1"></div>
<div class="balance-hero-orb balance-hero-orb-2"></div>
<div class="balance-hero-orb balance-hero-orb-3"></div>
<div class="balance-hero-grid"></div>
</div>
<div class="relative z-10 p-6 md:p-8 lg:p-10">
<!-- Top row -->
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-3">
<div class="balance-hero-icon">
<Icon name="wallet" size="md" />
</div>
<div>
<p class="text-sm font-medium text-white/60">可用余额</p>
</div>
</div>
<div class="balance-hero-currency">
{{ wallet?.currency || 'USD' }}
</div>
</div>
<!-- Balance number -->
<div class="balance-hero-amount">
<span class="balance-hero-dollar">$</span>
<span class="balance-hero-value">{{ wallet?.available_balance?.toFixed(2) ?? '0.00' }}</span>
</div>
<!-- Low balance warning -->
<div v-if="isLowBalance && wallet" class="balance-warning">
<Icon name="exclamationTriangle" size="xs" />
<span>余额低于预警线 ${{ wallet.warning_threshold?.toFixed(2) }}</span>
</div>
<!-- Secondary balances -->
<div class="flex items-center gap-3 mt-5 flex-wrap">
<button class="balance-chip group" @click="openFrozenModal">
<span class="balance-chip-dot bg-amber-400"></span>
<span class="text-white/50">冻结</span>
<span class="text-white font-semibold">${{ wallet?.frozen_balance?.toFixed(2) ?? '0.00' }}</span>
<Icon v-if="wallet?.frozen_balance > 0" name="chevronRight" size="xs"
class="text-white/30 group-hover:text-white/50 transition-colors" />
</button>
<div class="w-px h-4 bg-white/10"></div>
<div class="balance-chip-static">
<span class="balance-chip-dot bg-white/30"></span>
<span class="text-white/50">总余额</span>
<span class="text-white font-semibold">${{ wallet?.balance?.toFixed(2) ?? '0.00' }}</span>
</div>
</div>
</div>
</div>
<!-- ============================================ -->
<!-- Two-column: Recharge + Current Plan -->
<!-- ============================================ -->
<div class="grid grid-cols-1 lg:grid-cols-5 gap-6">
<!-- Recharge Card -->
<div class="lg:col-span-3 card overflow-hidden">
<div class="card-header">
<div class="flex items-center gap-2.5">
<div class="h-8 w-8 rounded-lg bg-gradient-to-br from-primary-100 to-primary-50 flex items-center justify-center">
<Icon name="plus" size="sm" class="text-primary-600" />
</div>
<h2 class="font-semibold text-gray-900">充值</h2>
</div>
</div>
<div class="card-body space-y-6">
<!-- Amount Selection -->
<div>
<label class="input-label">充值金额(元)</label>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2.5 mt-2">
<button
v-for="amt in presetAmounts"
:key="amt"
class="amount-pill"
:class="rechargeAmount === amt ? 'amount-pill-active' : ''"
@click="selectPresetAmount(amt)"
>
<span class="amount-pill-symbol">¥</span>{{ amt }}
</button>
</div>
<div class="mt-3">
<input
v-model="customAmount"
type="number"
class="input"
placeholder="自定义金额"
min="1"
step="0.01"
@input="onCustomInput"
/>
</div>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
<!-- Balance Hero Card -->
<div class="lg:col-span-5">
<div class="balance-hero h-full">
<!-- Background decorations -->
<div class="balance-hero-bg">
<div class="balance-hero-orb balance-hero-orb-1"></div>
<div class="balance-hero-orb balance-hero-orb-2"></div>
<div class="balance-hero-grid"></div>
</div>
<!-- Payment Method Selection -->
<div v-if="payMethods.length > 0">
<label class="input-label">支付方式</label>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2.5 mt-2">
<button
v-for="method in payMethods"
:key="method.channel + '-' + method.type"
class="pay-method-card"
:class="selectedChannel === method.channel && selectedPaymentMethod === method.type
? 'pay-method-card-active'
: ''"
@click="selectPayMethod(method)"
>
<span
class="pay-method-icon"
:class="{
'pay-method-icon-alipay': method.type === 'alipay',
'pay-method-icon-wxpay': method.type === 'wxpay',
'pay-method-icon-default': method.type !== 'alipay' && method.type !== 'wxpay',
}"
>
<template v-if="method.type === 'alipay'">支</template>
<template v-else-if="method.type === 'wxpay'">微</template>
<template v-else>{{ method.name.substring(0, 1) }}</template>
</span>
<span class="text-sm font-medium text-gray-700">{{ method.name }}</span>
</button>
</div>
</div>
<div v-else class="py-6 text-center">
<p class="text-sm text-gray-400">暂无可用的支付渠道,请联系管理员配置</p>
</div>
<!-- Submit -->
<button
class="btn btn-primary btn-lg w-full"
:disabled="!rechargeAmount || !selectedChannel || !selectedPaymentMethod || rechargeLoading"
@click="handleRecharge"
>
<template v-if="rechargeLoading">
<span class="spinner"></span>
处理中...
</template>
<template v-else>
充值 ¥{{ rechargeAmount || '—' }}
</template>
</button>
</div>
</div>
<!-- Current Plan Card -->
<div class="lg:col-span-2">
<div v-if="currentPlanDetail" class="card h-full flex flex-col">
<div class="card-header">
<div class="flex items-center gap-2.5">
<div class="h-8 w-8 rounded-lg bg-gradient-to-br from-emerald-100 to-emerald-50 flex items-center justify-center">
<Icon name="shield" size="sm" class="text-emerald-600" />
</div>
<h2 class="font-semibold text-gray-900">当前套餐</h2>
</div>
<span class="badge badge-success">使用中</span>
</div>
<div class="card-body flex-1 flex flex-col">
<h3 class="text-lg font-semibold text-gray-900 mb-1">{{ currentPlanDetail.name }}</h3>
<p class="text-xs text-gray-400 mb-5">{{ currentPlanDetail.description || '标准套餐方案' }}</p>
<div class="space-y-3 flex-1">
<div class="plan-detail-row">
<span class="text-gray-500">月费</span>
<span class="font-semibold text-gray-900">¥{{ Number(currentPlanDetail.monthly_price).toFixed(0) }}<span class="text-gray-400 font-normal text-xs">/月</span></span>
</div>
<div class="plan-detail-row">
<span class="text-gray-500">Token 额度</span>
<span v-if="currentPlanDetail.monthly_quota_tokens > 0" class="font-medium text-gray-700">
{{ currentPlanDetail.monthly_quota_tokens.toLocaleString() }}/月
</span>
<span v-else class="font-medium text-primary-600">不限</span>
</div>
<div v-if="currentPlan.value?.expires_at" class="plan-detail-row">
<span class="text-gray-500">到期时间</span>
<span class="font-medium text-gray-700">
{{ new Date(currentPlan.value.expires_at * 1000).toLocaleDateString('zh-CN') }}
</span>
</div>
</div>
<div class="mt-5 pt-4 border-t border-gray-100">
<a href="#plans" class="text-sm text-primary-600 font-medium hover:text-primary-700 transition-colors inline-flex items-center gap-1">
更换套餐
<Icon name="chevronRight" size="xs" />
</a>
</div>
</div>
</div>
<!-- No current plan -->
<div v-else class="card h-full">
<div class="card-header">
<div class="flex items-center gap-2.5">
<div class="h-8 w-8 rounded-lg bg-gray-50 flex items-center justify-center">
<Icon name="exclamationCircle" size="sm" class="text-gray-400" />
</div>
<h2 class="font-semibold text-gray-900">当前套餐</h2>
</div>
</div>
<div class="card-body flex flex-col items-center justify-center py-12">
<div class="w-14 h-14 rounded-2xl bg-gray-50 flex items-center justify-center mb-4">
<Icon name="creditCard" size="lg" class="text-gray-300" />
</div>
<p class="text-sm text-gray-500 mb-1">暂无订阅套餐</p>
<a href="#plans" class="text-sm text-primary-600 font-medium hover:text-primary-700 transition-colors">
查看可用套餐 &rarr;
</a>
</div>
</div>
</div>
</div>
<!-- ============================================ -->
<!-- Subscription Plans -->
<!-- ============================================ -->
<div id="plans" class="card">
<div class="card-header">
<div class="flex items-center gap-2.5">
<div class="h-8 w-8 rounded-lg bg-gradient-to-br from-violet-100 to-violet-50 flex items-center justify-center">
<Icon name="chart" size="sm" class="text-violet-600" />
</div>
<div>
<h2 class="font-semibold text-gray-900">套餐方案</h2>
<p class="text-xs text-gray-400 mt-0.5">选择适合您团队的方案</p>
</div>
</div>
</div>
<div class="card-body">
<!-- Loading -->
<div v-if="loading" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
<div v-for="i in 3" :key="i" class="h-52 rounded-2xl border border-gray-100 p-5 animate-pulse">
<div class="h-4 bg-gray-100 rounded w-1/3 mb-3"></div>
<div class="h-8 bg-gray-100 rounded w-2/3 mb-4"></div>
<div class="h-3 bg-gray-100 rounded w-full mb-2"></div>
<div class="h-3 bg-gray-100 rounded w-3/4"></div>
</div>
</div>
<!-- Empty -->
<div v-else-if="plans.length === 0" class="py-12 text-center">
<div class="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-gray-50 mb-4">
<Icon name="exclamationCircle" size="lg" class="text-gray-300" />
</div>
<p class="text-gray-500 text-sm">暂无可用套餐,请联系管理员配置</p>
</div>
<!-- Plan Cards -->
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
<div
v-for="plan in plans"
:key="plan.id"
class="plan-card group"
:class="{
'plan-card-recommended': plan.is_recommended,
'plan-card-current': isCurrentPlan(plan),
}"
>
<!-- Recommended accent bar -->
<div v-if="plan.is_recommended" class="plan-card-accent"></div>
<div class="p-5">
<!-- Header row -->
<div class="flex items-start justify-between mb-4">
<h3 class="text-base font-semibold text-gray-900">{{ plan.name }}</h3>
<div class="flex items-center gap-1.5">
<span v-if="plan.is_recommended && !isCurrentPlan(plan)" class="badge badge-primary text-xs">推荐</span>
<span v-if="isCurrentPlan(plan)" class="badge badge-success text-xs">当前</span>
<div class="relative z-10 p-6 md:p-8">
<!-- Top row -->
<div class="flex items-center justify-between mb-5">
<div class="flex items-center gap-3">
<div class="balance-hero-icon">
<Icon name="wallet" size="md" />
</div>
<p class="text-sm font-medium text-white/60">可用余额</p>
</div>
<!-- Price -->
<div class="mb-5">
<div class="flex items-baseline gap-1">
<span class="text-xs text-gray-400">¥</span>
<span class="text-3xl font-bold text-gray-900 tracking-tight">
{{ Number(plan.monthly_price) === 0 ? '免费' : Number(plan.monthly_price).toFixed(0) }}
</span>
<span v-if="Number(plan.monthly_price) > 0" class="text-sm text-gray-400">/月</span>
</div>
<p v-if="Number(plan.yearly_price) > 0" class="text-xs text-primary-600 mt-1.5 font-medium">
年付 ¥{{ Number(plan.yearly_price).toFixed(0) }},省 ¥{{ (Number(plan.monthly_price) * 12 - Number(plan.yearly_price)).toFixed(0) }}
</p>
<div class="balance-hero-currency">
{{ wallet?.currency || 'USD' }}
</div>
</div>
<!-- Features -->
<div class="space-y-2.5 mb-5">
<!-- Balance number -->
<div class="balance-hero-amount">
<span class="balance-hero-dollar">$</span>
<span class="balance-hero-value">{{ wallet?.available_balance?.toFixed(2) ?? '0.00' }}</span>
</div>
<!-- Low balance warning -->
<div v-if="isLowBalance && wallet" class="balance-warning">
<Icon name="exclamationTriangle" size="xs" />
<span>余额低于预警线 ${{ wallet.warning_threshold?.toFixed(2) }}</span>
</div>
<!-- Secondary balances -->
<div class="flex flex-col gap-3 mt-6">
<button class="balance-chip group" @click="openFrozenModal">
<div class="flex items-center gap-2.5">
<div class="plan-card-check">
<Icon name="check" size="xs" />
</div>
<span class="text-sm text-gray-600">
{{ plan.monthly_quota_tokens > 0 ? plan.monthly_quota_tokens.toLocaleString() + ' Tokens/月' : '不限 Token 用量' }}
</span>
<span class="balance-chip-dot bg-amber-400"></span>
<span class="text-white/50 text-sm">冻结金额</span>
</div>
<div v-if="plan.description" class="flex items-center gap-2.5">
<div class="plan-card-check">
<Icon name="check" size="xs" />
</div>
<span class="text-sm text-gray-600 line-clamp-1">{{ plan.description }}</span>
<div class="flex items-center gap-1.5">
<span class="text-white font-semibold text-sm">${{ wallet?.frozen_balance?.toFixed(2) ?? '0.00' }}</span>
<Icon v-if="wallet?.frozen_balance > 0" name="chevronRight" size="xs"
class="text-white/30 group-hover:text-white/50 transition-colors" />
</div>
</div>
</button>
<!-- Action -->
<button
v-if="isCurrentPlan(plan)"
class="btn btn-secondary w-full btn-sm"
disabled
>
当前方案
</button>
<button
v-else
class="btn w-full btn-sm"
:class="plan.is_recommended ? 'btn-primary' : 'btn-secondary hover:!border-primary-300 hover:!text-primary-600'"
@click="openConfirm(plan)"
>
{{ Number(plan.monthly_price) === 0 ? '免费开通' : '立即购买' }}
</button>
<div class="balance-chip-static">
<div class="flex items-center gap-2.5">
<span class="balance-chip-dot bg-white/20"></span>
<span class="text-white/50 text-sm">总余额</span>
</div>
<span class="text-white font-semibold text-sm">${{ wallet?.balance?.toFixed(2) ?? '0.00' }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ============================================ -->
<!-- Confirm Subscribe Modal -->
<!-- ============================================ -->
<Teleport to="body">
<transition name="modal">
<div v-if="showConfirm" class="modal-overlay" @click.self="showConfirm = false">
<div class="modal-content w-full max-w-md">
<div class="modal-header">
<h3 class="modal-title">确认订阅</h3>
<button @click="showConfirm = false" class="btn-ghost btn-icon">
<Icon name="x" size="md" />
</button>
</div>
<div class="modal-body">
<div v-if="selectedPlan" class="space-y-5">
<!-- Selected plan summary -->
<div class="flex items-center justify-between p-4 bg-gray-50 rounded-xl">
<div>
<p class="font-medium text-gray-900">{{ selectedPlan.name }}</p>
<p class="text-xs text-gray-400 mt-0.5">{{ selectedPlan.identifier }}</p>
</div>
<span class="badge badge-primary">{{ selectedPlan.identifier }}</span>
</div>
<!-- Duration selection -->
<div>
<label class="input-label">订阅时长</label>
<div class="grid grid-cols-2 gap-2 mt-2">
<button
v-for="opt in monthsOptions"
:key="opt.value"
class="rounded-xl px-3 py-2.5 text-sm font-medium border-2 transition-all"
:class="selectedMonths === opt.value
? 'border-primary-500 bg-primary-50 text-primary-700'
: 'border-gray-200 text-gray-600 hover:border-gray-300'"
@click="selectedMonths = opt.value"
>
{{ opt.label }}
</button>
</div>
</div>
<!-- Price summary -->
<div class="flex items-center justify-between p-4 rounded-xl bg-gradient-to-r from-primary-50 to-primary-100/50 border border-primary-200/50">
<span class="text-sm text-gray-600">应付金额</span>
<span class="text-xl font-bold text-primary-600">
¥{{ calcPrice(selectedPlan, selectedMonths).toFixed(2) }}
</span>
</div>
<!-- Recharge Card -->
<div class="lg:col-span-7">
<div class="card h-full">
<div class="card-header">
<div class="flex items-center gap-2.5">
<div class="h-8 w-8 rounded-lg bg-gradient-to-br from-primary-100 to-primary-50 flex items-center justify-center">
<Icon name="plus" size="sm" class="text-primary-600" />
</div>
</div>
<div class="modal-footer">
<button @click="showConfirm = false" class="btn btn-secondary">取消</button>
<button
class="btn btn-primary"
:disabled="confirmLoading"
@click="handleSubscribe"
>
<template v-if="confirmLoading">
<span class="spinner"></span>
处理中...
</template>
<template v-else>确认订阅</template>
</button>
<h2 class="font-semibold text-gray-900">充值</h2>
</div>
</div>
<div class="card-body space-y-5">
<!-- Amount Selection -->
<div>
<label class="input-label">充值金额(元)</label>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2.5 mt-2">
<button
v-for="amt in presetAmounts"
:key="amt"
class="amount-pill"
:class="rechargeAmount === amt ? 'amount-pill-active' : ''"
@click="selectPresetAmount(amt)"
>
<span class="amount-pill-symbol">¥</span>{{ amt }}
</button>
</div>
<div class="mt-3">
<input
v-model="customAmount"
type="number"
class="input"
placeholder="自定义金额"
min="1"
step="0.01"
@input="onCustomInput"
/>
</div>
</div>
<!-- Payment Method Selection -->
<div v-if="payMethods.length > 0">
<label class="input-label">支付方式</label>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2.5 mt-2">
<button
v-for="method in payMethods"
:key="method.channel + '-' + method.type"
class="pay-method-card"
:class="selectedChannel === method.channel && selectedPaymentMethod === method.type
? 'pay-method-card-active'
: ''"
@click="selectPayMethod(method)"
>
<span
class="pay-method-icon"
:class="{
'pay-method-icon-alipay': method.type === 'alipay',
'pay-method-icon-wxpay': method.type === 'wxpay',
'pay-method-icon-default': method.type !== 'alipay' && method.type !== 'wxpay',
}"
>
<template v-if="method.type === 'alipay'">支</template>
<template v-else-if="method.type === 'wxpay'">微</template>
<template v-else>{{ method.name.substring(0, 1) }}</template>
</span>
<span class="text-sm font-medium text-gray-700">{{ method.name }}</span>
</button>
</div>
</div>
<div v-else class="py-6 text-center">
<p class="text-sm text-gray-400">暂无可用的支付渠道,请联系管理员配置</p>
</div>
<!-- Submit -->
<button
class="btn btn-primary btn-lg w-full"
:disabled="!rechargeAmount || !selectedChannel || !selectedPaymentMethod || rechargeLoading"
@click="handleRecharge"
>
<template v-if="rechargeLoading">
<span class="spinner"></span>
处理中...
</template>
<template v-else>
充值 ¥{{ rechargeAmount || '—' }}
</template>
</button>
</div>
</div>
</transition>
</Teleport>
</div>
</div>
<!-- ============================================ -->
<!-- Frozen Items Modal -->
@@ -961,7 +637,7 @@ onBeforeUnmount(() => {
}
/* ==========================================
Hero Balance Card
Balance Hero Card
========================================== */
.balance-hero {
position: relative; overflow: hidden;
@@ -981,10 +657,6 @@ onBeforeUnmount(() => {
bottom: -3rem; left: -3rem; height: 14rem; width: 14rem;
background: radial-gradient(circle, rgba(94, 234, 212, 0.1) 0%, transparent 70%);
}
.balance-hero-orb-3 {
top: 33%; right: 25%; height: 8rem; width: 8rem;
background: radial-gradient(circle, rgba(20, 184, 166, 0.08) 0%, transparent 70%);
}
.balance-hero-grid {
position: absolute; inset: 0;
background-image: radial-gradient(circle at 1px 1px, rgba(255, 255, 255, 0.04) 1px, transparent 0);
@@ -1013,8 +685,8 @@ onBeforeUnmount(() => {
font-size: 2.25rem; font-weight: 700; color: white;
letter-spacing: -0.025em; font-variant-numeric: tabular-nums;
}
@media (min-width: 768px) {
.balance-hero-value { font-size: 3rem; }
@media (min-width: 1024px) {
.balance-hero-value { font-size: 2.5rem; }
}
.balance-warning {
margin-top: 0.75rem;
@@ -1025,16 +697,17 @@ onBeforeUnmount(() => {
border: 1px solid rgba(251, 191, 36, 0.2);
}
.balance-chip {
display: inline-flex; align-items: center; gap: 0.5rem;
border-radius: 0.75rem; padding: 0.5rem 0.875rem;
display: flex; align-items: center; justify-content: space-between;
border-radius: 0.75rem; padding: 0.625rem 0.875rem;
transition: all 0.2s;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.04);
width: 100%;
}
.balance-chip:hover { background: rgba(255, 255, 255, 0.1); }
.balance-chip-static {
display: inline-flex; align-items: center; gap: 0.5rem;
padding: 0 0.25rem;
display: flex; align-items: center; justify-content: space-between;
padding: 0.625rem 0.875rem;
}
.balance-chip-dot {
height: 0.375rem; width: 0.375rem;
@@ -1084,48 +757,6 @@ onBeforeUnmount(() => {
.pay-method-icon-wxpay { background: linear-gradient(135deg, #07c160, #06ad56); }
.pay-method-icon-default { background: linear-gradient(135deg, #6b7280, #4b5563); }
/* ==========================================
Plan Detail Rows
========================================== */
.plan-detail-row {
display: flex; align-items: center;
justify-content: space-between;
font-size: 0.875rem; padding: 0.25rem 0;
}
/* ==========================================
Plan Cards
========================================== */
.plan-card {
position: relative; border-radius: 1rem;
border: 1px solid #e5e7eb; background: white;
overflow: hidden; transition: all 0.3s;
}
.plan-card:hover {
border-color: #d1d5db;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
}
.plan-card-recommended {
border-color: #99f6e4;
background: linear-gradient(180deg, rgba(240, 253, 250, 0.5) 0%, white 30%);
}
.plan-card-recommended:hover { border-color: #5eead4; }
.plan-card-current {
border-color: #a7f3d0;
background-color: rgba(236, 253, 245, 0.3);
}
.plan-card-current:hover { border-color: #6ee7b7; }
.plan-card-accent {
height: 0.25rem; width: 100%;
background: linear-gradient(90deg, #2dd4bf, #14b8a6, #0d9488);
}
.plan-card-check {
height: 1.25rem; width: 1.25rem; border-radius: 0.375rem;
background-color: #f0fdfa;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0; color: #14b8a6;
}
/* ==========================================
Transitions
========================================== */
@@ -1142,9 +773,6 @@ onBeforeUnmount(() => {
.wallet-page > *:nth-child(1) { animation-delay: 0ms; }
.wallet-page > *:nth-child(2) { animation-delay: 60ms; }
.wallet-page > *:nth-child(3) { animation-delay: 120ms; }
.wallet-page > *:nth-child(4) { animation-delay: 180ms; }
.wallet-page > *:nth-child(5) { animation-delay: 240ms; }
.wallet-page > *:nth-child(6) { animation-delay: 300ms; }
@keyframes fade-in {
from { opacity: 0; transform: translateY(8px); }