Files
quyun-v2/frontend/portal/src/views/user/WalletView.vue

163 lines
7.1 KiB
Vue

<template>
<div class="bg-white rounded-xl shadow-sm border border-slate-100 min-h-[600px] p-8">
<h1 class="text-2xl font-bold text-slate-900 mb-8">我的钱包</h1>
<!-- Balance Card -->
<div class="bg-gradient-to-r from-blue-600 to-blue-500 rounded-2xl p-8 text-white shadow-lg mb-10 relative overflow-hidden">
<!-- Background Decor -->
<div class="absolute -right-10 -bottom-10 w-48 h-48 bg-white/10 rounded-full blur-2xl"></div>
<div class="relative z-10 flex flex-col md:flex-row justify-between items-center gap-6">
<div>
<div class="text-blue-100 text-sm font-medium mb-1">账户余额 ()</div>
<div class="text-5xl font-bold font-mono">{{ balance.toFixed(2) }}</div>
</div>
<button @click="showRecharge = !showRecharge" class="px-8 py-3 bg-white text-blue-600 font-bold rounded-full shadow-sm hover:bg-blue-50 transition-all active:scale-95 cursor-pointer">
立即充值
</button>
</div>
</div>
<!-- Recharge Section (Collapsible) -->
<div v-if="showRecharge" class="mb-10 p-6 bg-slate-50 rounded-xl border border-slate-200 animate-in fade-in slide-in-from-top-4 duration-300">
<h3 class="font-bold text-slate-900 mb-4">充值金额</h3>
<div class="grid grid-cols-3 sm:grid-cols-4 gap-4 mb-6">
<button
v-for="amount in [10, 30, 50, 100, 200, 500]"
:key="amount"
@click="selectedAmount = amount; customAmount = ''"
class="h-14 rounded-lg border-2 font-bold text-lg transition-all cursor-pointer active:scale-95"
:class="selectedAmount === amount ? 'border-blue-600 bg-blue-50 text-blue-600' : 'border-slate-200 bg-white text-slate-600 hover:border-blue-300'"
>
{{ amount }}
</button>
<div class="col-span-2 sm:col-span-2 relative">
<input
v-model="customAmount"
@focus="selectedAmount = null"
type="number"
placeholder="自定义金额"
class="w-full h-14 pl-4 pr-4 rounded-lg border-2 border-slate-200 focus:border-blue-600 focus:outline-none text-lg font-bold transition-colors"
>
</div>
</div>
<h3 class="font-bold text-slate-900 mb-4">支付方式</h3>
<div class="flex gap-4 mb-8">
<button
@click="paymentMethod = 'wechat'"
class="flex items-center gap-2 px-6 py-3 border-2 rounded-lg font-medium cursor-pointer active:scale-95 transition-transform"
:class="paymentMethod === 'wechat' ? 'border-blue-600 bg-blue-50 text-blue-700' : 'border-slate-200 text-slate-600 hover:bg-white hover:border-slate-300'"
>
<i class="pi pi-wechat text-xl text-green-600"></i> 微信支付
</button>
<button
@click="paymentMethod = 'alipay'"
class="flex items-center gap-2 px-6 py-3 border-2 rounded-lg font-medium cursor-pointer active:scale-95 transition-transform"
:class="paymentMethod === 'alipay' ? 'border-blue-600 bg-blue-50 text-blue-700' : 'border-slate-200 text-slate-600 hover:bg-white hover:border-slate-300'"
>
<i class="pi pi-alipay text-xl text-blue-500"></i> 支付宝
</button>
</div>
<button
@click="handleRecharge"
:disabled="loading"
class="w-full py-4 bg-blue-600 text-white rounded-xl font-bold text-lg hover:bg-blue-700 transition-all shadow-lg shadow-blue-200 cursor-pointer active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
>
<i v-if="loading" class="pi pi-spin pi-spinner mr-2"></i>
确认支付 ¥ {{ displayAmount }}
</button>
</div>
<!-- Transaction History -->
<div>
<h3 class="text-xl font-bold text-slate-900 mb-6 flex items-center gap-2">
<i class="pi pi-history text-slate-400"></i> 交易明细
</h3>
<div v-if="transactions.length > 0" class="space-y-4">
<div v-for="item in transactions" :key="item.id" class="flex items-center justify-between p-4 border-b border-slate-50 hover:bg-slate-50 rounded-lg transition-colors">
<div class="flex items-center gap-4">
<div class="w-10 h-10 rounded-full flex items-center justify-center" :class="item.type === 'income' ? 'bg-green-100 text-green-600' : 'bg-slate-100 text-slate-500'">
<i class="pi" :class="item.type === 'income' ? 'pi-plus' : 'pi-minus'"></i>
</div>
<div>
<div class="font-bold text-slate-900">{{ item.title || (item.type === 'income' ? '充值' : '消费') }}</div>
<div class="text-xs text-slate-400">{{ formatDate(item.date) }}</div>
</div>
</div>
<div class="font-mono font-bold text-lg" :class="item.type === 'income' ? 'text-green-600' : 'text-slate-900'">
{{ item.type === 'income' ? '+' : '-' }} {{ item.amount.toFixed(2) }}
</div>
</div>
</div>
<div v-else class="text-center py-10 text-slate-400">
暂无交易记录
</div>
</div>
<Toast />
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import Toast from 'primevue/toast';
import { useToast } from 'primevue/usetoast';
import { userApi } from '../../api/user';
import dayjs from 'dayjs';
const toast = useToast();
const showRecharge = ref(false);
const selectedAmount = ref(50);
const customAmount = ref('');
const balance = ref(0);
const transactions = ref([]);
const loading = ref(false);
const paymentMethod = ref('wechat');
const displayAmount = computed(() => {
return customAmount.value ? Number(customAmount.value).toFixed(2) : selectedAmount.value.toFixed(2);
});
const formatDate = (date) => {
return dayjs(date).format('YYYY-MM-DD HH:mm');
};
const fetchWallet = async () => {
try {
const res = await userApi.getWallet();
balance.value = res.balance || 0;
transactions.value = res.transactions || [];
} catch (error) {
console.error('Failed to fetch wallet:', error);
toast.add({ severity: 'error', summary: '错误', detail: '获取钱包信息失败', life: 3000 });
}
};
const handleRecharge = async () => {
const amount = Number(displayAmount.value);
if (!amount || amount <= 0) {
toast.add({ severity: 'warn', summary: '提示', detail: '请输入有效的充值金额', life: 3000 });
return;
}
loading.value = true;
try {
await userApi.recharge({ amount: amount, method: paymentMethod.value });
toast.add({ severity: 'success', summary: '成功', detail: '充值成功', life: 3000 });
showRecharge.value = false;
fetchWallet(); // Refresh balance
} catch (error) {
console.error('Recharge failed:', error);
toast.add({ severity: 'error', summary: '错误', detail: error.message || '充值失败', life: 3000 });
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchWallet();
});
</script>