1067 lines
23 KiB
Plaintext
1067 lines
23 KiB
Plaintext
<!-- 钱包页面 -->
|
||
<template>
|
||
<view class="wallet-page">
|
||
<!-- 顶部栏 -->
|
||
<!--<view class="wallet-header">
|
||
<text class="back-btn" @click="goBack">‹</text>
|
||
</view>-->
|
||
|
||
<scroll-view class="wallet-content" scroll-y>
|
||
<view class="dashboard-container">
|
||
<!-- 左侧/顶部区域:资产信息 -->
|
||
<view class="dashboard-main">
|
||
<!-- 余额概览 -->
|
||
<view class="balance-overview">
|
||
<text class="balance-label">账户余额</text>
|
||
<text class="balance-value">¥{{ balance.toFixed(2) }}</text>
|
||
<view class="balance-actions">
|
||
<button class="action-btn recharge" @click="recharge">充值</button>
|
||
<button class="action-btn withdraw" @click="withdraw">提现</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 资产统计 -->
|
||
<view class="assets-stats">
|
||
<view class="stat-item">
|
||
<text class="stat-label">累计充值</text>
|
||
<text class="stat-value">¥{{ stats.totalRecharge.toFixed(2) }}</text>
|
||
</view>
|
||
<view class="stat-item">
|
||
<text class="stat-label">累计消费</text>
|
||
<text class="stat-value">¥{{ stats.totalConsume.toFixed(2) }}</text>
|
||
</view>
|
||
<view class="stat-item">
|
||
<text class="stat-label">累计提现</text>
|
||
<text class="stat-value">¥{{ stats.totalWithdraw.toFixed(2) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 快捷功能 -->
|
||
<view class="quick-actions">
|
||
<view class="action-grid">
|
||
<view class="action-item" @click="goToCoupons">
|
||
<text class="action-icon">🎫</text>
|
||
<text class="action-text">优惠券</text>
|
||
</view>
|
||
<view class="action-item" @click="goToRedPackets">
|
||
<text class="action-icon">🧧</text>
|
||
<text class="action-text">红包</text>
|
||
</view>
|
||
<view class="action-item" @click="goToPoints">
|
||
<text class="action-icon">⭐</text>
|
||
<text class="action-text">积分</text>
|
||
</view>
|
||
<view class="action-item" @click="goToBankCards">
|
||
<text class="action-icon">💳</text>
|
||
<text class="action-text">银行卡</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 安全提示 (移动端在底部,PC端在左侧底部) -->
|
||
<view class="security-tips">
|
||
<text class="tip-title">安全提示</text>
|
||
<text class="tip-item">1. 请妥善保管您的支付密码</text>
|
||
<text class="tip-item">2. 不要向他人透露您的账户信息</text>
|
||
<text class="tip-item">3. 定期修改密码以确保账户安全</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 右侧/底部区域:交易记录 -->
|
||
<view class="dashboard-side">
|
||
<!-- 交易记录 -->
|
||
<view class="transactions-section">
|
||
<view class="section-header">
|
||
<text class="section-title">交易记录</text>
|
||
<view class="filter-tabs">
|
||
<text :class="['filter-tab', { active: activeFilter === 'all' }]"
|
||
@click="changeFilter('all')">全部</text>
|
||
<text :class="['filter-tab', { active: activeFilter === 'income' }]"
|
||
@click="changeFilter('income')">收入</text>
|
||
<text :class="['filter-tab', { active: activeFilter === 'expense' }]"
|
||
@click="changeFilter('expense')">支出</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 空状态 -->
|
||
<view v-if="transactions.length === 0 && !isLoading" class="empty-transactions">
|
||
<text class="empty-icon">💰</text>
|
||
<text class="empty-text">暂无交易记录</text>
|
||
<text class="empty-subtext">快去使用钱包功能吧</text>
|
||
</view>
|
||
|
||
<!-- 交易列表 -->
|
||
<view class="transactions-list">
|
||
<view v-for="transaction in transactions"
|
||
:key="transaction.id"
|
||
class="transaction-item">
|
||
<view class="transaction-left">
|
||
<text class="transaction-icon">{{ getTransactionIcon(transaction.type) }}</text>
|
||
<view class="transaction-info">
|
||
<text class="transaction-title">{{ getTransactionTitle(transaction.type) }}</text>
|
||
<text class="transaction-time">{{ formatTime(transaction.created_at) }}</text>
|
||
<text v-if="transaction.remark" class="transaction-remark">{{ transaction.remark }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="transaction-right">
|
||
<text :class="['transaction-amount',
|
||
{ income: transaction.amount > 0, expense: transaction.amount < 0 }]">
|
||
{{ transaction.amount > 0 ? '+' : '' }}¥{{ Math.abs(transaction.amount).toFixed(2) }}
|
||
</text>
|
||
<text class="transaction-balance">余额: ¥{{ transaction.current_balance.toFixed(2) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 加载更多 -->
|
||
<view v-if="isLoading" class="loading-more">
|
||
<text class="loading-text">加载中...</text>
|
||
</view>
|
||
<view v-if="!hasMore && transactions.length > 0" class="no-more">
|
||
<text class="no-more-text">没有更多记录了</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 充值弹窗 -->
|
||
<view v-if="showRechargePopup" class="recharge-popup">
|
||
<view class="popup-mask" @click="closeRechargePopup"></view>
|
||
<view class="popup-content">
|
||
<view class="popup-header">
|
||
<text class="popup-title">充值</text>
|
||
<text class="popup-close" @click="closeRechargePopup">×</text>
|
||
</view>
|
||
<view class="popup-body">
|
||
<text class="amount-label">充值金额</text>
|
||
<view class="amount-input">
|
||
<text class="currency-symbol">¥</text>
|
||
<input class="amount-field"
|
||
v-model="rechargeAmount"
|
||
type="number"
|
||
placeholder="请输入充值金额"
|
||
focus />
|
||
</view>
|
||
<view class="quick-amounts">
|
||
<text v-for="amount in quickAmounts"
|
||
:key="amount"
|
||
:class="['quick-amount', { active: rechargeAmount === amount.toString() }]"
|
||
@click="selectQuickAmount(amount)">
|
||
¥{{ amount }}
|
||
</text>
|
||
</view>
|
||
<text class="recharge-tip">单笔充值最低10元,最高5000元</text>
|
||
</view>
|
||
<view class="popup-footer">
|
||
<button class="cancel-btn" @click="closeRechargePopup">取消</button>
|
||
<button class="confirm-btn"
|
||
:class="{ disabled: !canRecharge }"
|
||
@click="confirmRecharge">
|
||
确认充值
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, computed, watch } from 'vue'
|
||
import { onShow } from '@dcloudio/uni-app'
|
||
import { supabaseService } from '@/utils/supabaseService.uts'
|
||
|
||
type WalletType = {
|
||
id: string
|
||
user_id: string
|
||
balance: number
|
||
total_recharge: number
|
||
total_consume: number
|
||
total_withdraw: number
|
||
updated_at: string
|
||
}
|
||
|
||
type TransactionType = {
|
||
id: string
|
||
user_id: string
|
||
change_amount: number
|
||
current_balance: number
|
||
change_type: string // 'recharge' | 'consume' | 'withdraw' | 'refund' | 'reward'
|
||
related_id: string | null
|
||
remark: string | null
|
||
created_at: string
|
||
}
|
||
|
||
type StatsType = {
|
||
totalRecharge: number
|
||
totalConsume: number
|
||
totalWithdraw: number
|
||
}
|
||
|
||
const balance = ref<number>(0)
|
||
const stats = ref<StatsType>({
|
||
totalRecharge: 0,
|
||
totalConsume: 0,
|
||
totalWithdraw: 0
|
||
})
|
||
const transactions = ref<Array<TransactionType>>([])
|
||
const activeFilter = ref<string>('all')
|
||
const isLoading = ref<boolean>(false)
|
||
const currentPage = ref<number>(1)
|
||
const pageSize = ref<number>(20)
|
||
const hasMore = ref<boolean>(true)
|
||
const showRechargePopup = ref<boolean>(false)
|
||
const rechargeAmount = ref<string>('')
|
||
const quickAmounts = [50, 100, 200, 500, 1000]
|
||
|
||
// 计算属性
|
||
const canRecharge = computed(() => {
|
||
const amount = parseFloat(rechargeAmount.value)
|
||
return !isNaN(amount) && amount >= 10 && amount <= 5000
|
||
})
|
||
|
||
// 监听过滤器变化
|
||
watch(activeFilter, () => {
|
||
resetTransactions()
|
||
loadTransactions()
|
||
})
|
||
|
||
// 生命周期
|
||
onShow(() => {
|
||
loadWalletData()
|
||
})
|
||
|
||
// 重置交易记录
|
||
const resetTransactions = () => {
|
||
transactions.value = []
|
||
currentPage.value = 1
|
||
hasMore.value = true
|
||
}
|
||
|
||
// 加载钱包数据
|
||
const loadWalletData = async () => {
|
||
const userId = getCurrentUserId()
|
||
if (userId == '') {
|
||
// uni.navigateTo({
|
||
// url: '/pages/user/login'
|
||
// })
|
||
return
|
||
}
|
||
|
||
await Promise.all([
|
||
loadBalance(),
|
||
loadTransactions()
|
||
])
|
||
}
|
||
|
||
// 加载余额信息
|
||
const loadBalance = async () => {
|
||
try {
|
||
// 调用 Supabase 服务获取真实余额
|
||
const realBalance = await supabaseService.getUserBalance()
|
||
balance.value = realBalance
|
||
|
||
// 统计数据暂时保持 mock 或设为 0,因为后端还未实现具体统计接口
|
||
stats.value = {
|
||
totalRecharge: 0,
|
||
totalConsume: 0,
|
||
totalWithdraw: 0
|
||
}
|
||
} catch (err) {
|
||
console.error('加载钱包异常:', err)
|
||
}
|
||
}
|
||
|
||
// 加载交易记录
|
||
const loadTransactions = async (loadMore: boolean = false) => {
|
||
if (isLoading.value || (!hasMore.value && loadMore)) {
|
||
return
|
||
}
|
||
|
||
isLoading.value = true
|
||
|
||
try {
|
||
const userId = getCurrentUserId()
|
||
if (userId == '') {
|
||
isLoading.value = false
|
||
return
|
||
}
|
||
|
||
const page = loadMore ? currentPage.value + 1 : 1
|
||
const limit = 20
|
||
|
||
// 使用 Supabase 获取真实数据
|
||
// 注意:目前后端接口暂不支持 activeFilter 筛选,会返回所有记录
|
||
const data = await supabaseService.getTransactions(page, limit)
|
||
|
||
const mappedData: TransactionType[] = []
|
||
for (let i = 0; i < data.length; i++) {
|
||
const item = data[i]
|
||
let id = ''
|
||
let amount = 0
|
||
let balance = 0
|
||
let type = ''
|
||
let remark = ''
|
||
let createdAt = ''
|
||
|
||
if (item instanceof UTSJSONObject) {
|
||
id = item.getString('id') ?? ''
|
||
amount = item.getNumber('amount') ?? 0
|
||
balance = item.getNumber('balance_after') ?? 0
|
||
type = item.getString('type') ?? 'consume'
|
||
remark = item.getString('description') ?? ''
|
||
createdAt = item.getString('created_at') ?? ''
|
||
} else {
|
||
id = (item['id'] as string) ?? ''
|
||
amount = (item['amount'] as number) ?? 0
|
||
balance = (item['balance_after'] as number) ?? 0
|
||
type = (item['type'] as string) ?? 'consume'
|
||
remark = (item['description'] as string) ?? ''
|
||
createdAt = (item['created_at'] as string) ?? ''
|
||
}
|
||
|
||
mappedData.push({
|
||
id: id,
|
||
user_id: userId,
|
||
change_amount: amount,
|
||
current_balance: balance,
|
||
change_type: type,
|
||
related_id: null,
|
||
remark: remark,
|
||
created_at: createdAt
|
||
})
|
||
}
|
||
|
||
if (loadMore) {
|
||
transactions.value.push(...mappedData)
|
||
} else {
|
||
transactions.value = mappedData
|
||
}
|
||
|
||
if (mappedData.length < limit) {
|
||
hasMore.value = false
|
||
} else {
|
||
hasMore.value = true
|
||
}
|
||
|
||
currentPage.value = page
|
||
} catch (err) {
|
||
console.error('加载交易记录异常:', err)
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 获取当前用户ID
|
||
const getCurrentUserId = (): string => {
|
||
const userStore = uni.getStorageSync('userInfo')
|
||
return userStore?.getString('id') ?? ''
|
||
}
|
||
|
||
// 获取交易图标
|
||
const getTransactionIcon = (type: string): string => {
|
||
const icons: Record<string, string> = {
|
||
recharge: '💳',
|
||
consume: '🛒',
|
||
withdraw: '🏦',
|
||
refund: '🔄',
|
||
reward: '🎁',
|
||
income: '💰',
|
||
expense: '📤'
|
||
}
|
||
const icon = icons[type]
|
||
return icon != null ? icon : '💰'
|
||
}
|
||
|
||
// 获取交易标题
|
||
const getTransactionTitle = (type: string): string => {
|
||
const titles: Record<string, string> = {
|
||
recharge: '账户充值',
|
||
consume: '商品消费',
|
||
withdraw: '余额提现',
|
||
refund: '订单退款',
|
||
reward: '活动奖励',
|
||
income: '收入',
|
||
expense: '支出'
|
||
}
|
||
const title = titles[type]
|
||
return title != null ? title : '交易'
|
||
}
|
||
|
||
// 格式化时间
|
||
const formatTime = (timeStr: string): string => {
|
||
const date = new Date(timeStr)
|
||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||
const day = date.getDate().toString().padStart(2, '0')
|
||
const hours = date.getHours().toString().padStart(2, '0')
|
||
const minutes = date.getMinutes().toString().padStart(2, '0')
|
||
return `${month}-${day} ${hours}:${minutes}`
|
||
}
|
||
|
||
// 显示更多操作
|
||
const showMoreActions = () => {
|
||
uni.showActionSheet({
|
||
itemList: ['交易记录', '安全设置', '帮助中心'],
|
||
success: (res) => {
|
||
switch (res.tapIndex) {
|
||
case 0:
|
||
// 交易记录已经在当前页
|
||
break
|
||
case 1:
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/settings'
|
||
})
|
||
break
|
||
case 2:
|
||
uni.navigateTo({
|
||
url: '/pages/info/help'
|
||
})
|
||
break
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 充值
|
||
const recharge = () => {
|
||
showRechargePopup.value = true
|
||
rechargeAmount.value = ''
|
||
}
|
||
|
||
// 提现
|
||
const withdraw = () => {
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/withdraw'
|
||
})
|
||
}
|
||
|
||
// 跳转到优惠券
|
||
const goToCoupons = () => {
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/coupons'
|
||
})
|
||
}
|
||
|
||
// 跳转到红包
|
||
const goToRedPackets = () => {
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/red-packets/index'
|
||
})
|
||
}
|
||
|
||
// 跳转到积分
|
||
const goToPoints = () => {
|
||
// 使用统一的积分页面
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/points/index'
|
||
})
|
||
}
|
||
|
||
// 跳转到银行卡
|
||
const goToBankCards = () => {
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/bank-cards/index'
|
||
})
|
||
}
|
||
|
||
// 切换过滤器
|
||
const changeFilter = (filter: string) => {
|
||
activeFilter.value = filter
|
||
}
|
||
|
||
// 加载更多
|
||
const loadMore = () => {
|
||
if (hasMore.value && !isLoading.value) {
|
||
loadTransactions(true)
|
||
}
|
||
}
|
||
|
||
// 选择快捷金额
|
||
const selectQuickAmount = (amount: number) => {
|
||
rechargeAmount.value = amount.toString()
|
||
}
|
||
|
||
// 确认充值
|
||
const confirmRecharge = async () => {
|
||
if (!canRecharge.value) return
|
||
|
||
const amount = parseFloat(rechargeAmount.value)
|
||
if (isNaN(amount)) return
|
||
|
||
uni.showLoading({ title: '处理中...' })
|
||
try {
|
||
const success = await supabaseService.rechargeBalance(amount)
|
||
if (success) {
|
||
uni.showToast({
|
||
title: '充值成功',
|
||
icon: 'success'
|
||
})
|
||
closeRechargePopup()
|
||
// 刷新数据
|
||
loadWalletData()
|
||
} else {
|
||
uni.showToast({
|
||
title: '充值失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (e) {
|
||
console.error('充值异常:', e)
|
||
uni.showToast({
|
||
title: '系统异常,请稍后重试',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
|
||
// 关闭充值弹窗
|
||
const closeRechargePopup = () => {
|
||
showRechargePopup.value = false
|
||
rechargeAmount.value = ''
|
||
}
|
||
|
||
// 返回
|
||
const goBack = () => {
|
||
uni.navigateBack()
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 基础样式 */
|
||
.wallet-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1; /* Fixed 100vh */
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.wallet-content {
|
||
flex: 1;
|
||
}
|
||
|
||
.dashboard-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-bottom: 20px;
|
||
}
|
||
|
||
.dashboard-main {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.dashboard-side {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* 响应式布局优化 */
|
||
@media screen and (min-width: 768px) {
|
||
.wallet-content {
|
||
padding: 20px;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.dashboard-container {
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
width: 100%;
|
||
}
|
||
|
||
.balance-overview, .assets-stats, .quick-actions, .transactions-section, .security-tips {
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.popup-content {
|
||
width: 400px;
|
||
left: 50%;
|
||
bottom: 50%;
|
||
transform: translate(-50%, 50%);
|
||
border-radius: 15px;
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 1024px) {
|
||
.wallet-page {
|
||
flex-direction: column; /* 保持纵向,内容区内部处理横向 */
|
||
}
|
||
|
||
.wallet-content {
|
||
width: 100%;
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.dashboard-container {
|
||
flex-direction: row; /* 横向排列 */
|
||
align-items: flex-start;
|
||
/* gap: 20px; REMOVED */
|
||
/* max-width: 100%; REMOVED */
|
||
}
|
||
|
||
.dashboard-main {
|
||
width: 400px; /* 左侧固定宽度 */
|
||
flex-shrink: 0;
|
||
margin-right: 20px; /* REPLACED gap */
|
||
}
|
||
|
||
.dashboard-side {
|
||
flex: 1; /* 右侧自适应 */
|
||
min-width: 0;
|
||
}
|
||
|
||
/* 调整各模块间距 */
|
||
.balance-overview,
|
||
.assets-stats,
|
||
.quick-actions,
|
||
.security-tips {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.transactions-section {
|
||
margin-top: 0; /* 移除顶部间距,与左侧对齐 */
|
||
height: 100%;
|
||
min-height: 600px; /* 保证右侧高度 */
|
||
}
|
||
}
|
||
|
||
/* 模块样式 */
|
||
.balance-overview {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
padding: 30px 20px;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.balance-label {
|
||
/* display: block; REMOVED */
|
||
font-size: 14px;
|
||
opacity: 0.9;
|
||
margin-bottom: 10px;
|
||
text-align: center;
|
||
}
|
||
|
||
.balance-value {
|
||
/* display: block; REMOVED */
|
||
font-size: 36px;
|
||
font-weight: bold;
|
||
margin-bottom: 20px;
|
||
text-align: center;
|
||
}
|
||
|
||
.balance-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
/* gap: 20px; REMOVED */
|
||
}
|
||
|
||
.action-btn {
|
||
flex: 1;
|
||
height: 40px;
|
||
border-radius: 20px;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
border: none;
|
||
}
|
||
|
||
.action-btn.recharge {
|
||
background-color: #ffffff;
|
||
color: #667eea;
|
||
margin-right: 20px; /* REPLACED gap */
|
||
}
|
||
|
||
.action-btn.withdraw {
|
||
background-color: rgba(255, 255, 255, 0.2);
|
||
color: #ffffff;
|
||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||
}
|
||
|
||
.assets-stats {
|
||
background-color: #ffffff;
|
||
padding: 20px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.stat-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-label {
|
||
/* display: block; REMOVED */
|
||
font-size: 12px;
|
||
color: #666666;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.stat-value {
|
||
/* display: block; REMOVED */
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.quick-actions {
|
||
background-color: #ffffff;
|
||
margin-top: 10px;
|
||
padding: 20px;
|
||
}
|
||
|
||
.action-grid {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.action-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
flex: 1;
|
||
}
|
||
|
||
.action-icon {
|
||
font-size: 28px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.action-text {
|
||
font-size: 12px;
|
||
color: #666666;
|
||
}
|
||
|
||
.transactions-section {
|
||
background-color: #ffffff;
|
||
margin-top: 10px;
|
||
padding: 15px;
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.filter-tabs {
|
||
/* gap: 15px; REMOVED */
|
||
}
|
||
|
||
.filter-tab {
|
||
font-size: 14px;
|
||
color: #666666;
|
||
padding: 5px 0;
|
||
position: relative;
|
||
margin-right: 15px; /* REPLACED gap */
|
||
border-bottom: 2px solid transparent; /* Prepare for active state */
|
||
}
|
||
|
||
.filter-tab.active {
|
||
color: #007aff;
|
||
font-weight: bold;
|
||
border-bottom: 2px solid #007aff; /* REPLACED ::after */
|
||
}
|
||
|
||
/* ::after removed */
|
||
|
||
.empty-transactions {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40px 20px;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 60px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 16px;
|
||
color: #666666;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.empty-subtext {
|
||
font-size: 14px;
|
||
color: #999999;
|
||
}
|
||
|
||
.transactions-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.transaction-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
padding: 15px 0;
|
||
border-bottom: 1px solid #f5f5f5;
|
||
}
|
||
|
||
.transaction-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.transaction-left {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.transaction-icon {
|
||
font-size: 24px;
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.transaction-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.transaction-title {
|
||
font-size: 14px;
|
||
color: #333333;
|
||
font-weight: bold;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.transaction-time {
|
||
font-size: 12px;
|
||
color: #999999;
|
||
margin-bottom: 3px;
|
||
}
|
||
|
||
.transaction-remark {
|
||
font-size: 12px;
|
||
color: #666666;
|
||
}
|
||
|
||
.transaction-right {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.transaction-amount {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.transaction-amount.income {
|
||
color: #4caf50;
|
||
}
|
||
|
||
.transaction-amount.expense {
|
||
color: #333333;
|
||
}
|
||
|
||
.transaction-balance {
|
||
font-size: 12px;
|
||
color: #999999;
|
||
}
|
||
|
||
.loading-more,
|
||
.no-more {
|
||
padding: 20px;
|
||
text-align: center;
|
||
}
|
||
|
||
.loading-text,
|
||
.no-more-text {
|
||
color: #999999;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.security-tips {
|
||
background-color: #ffffff;
|
||
margin-top: 10px;
|
||
padding: 20px;
|
||
}
|
||
|
||
.tip-title {
|
||
/* display: block; REMOVED */
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
/* display: block; REMOVED */
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.tip-item {
|
||
/* display: block; REMOVED */
|
||
margin-bottom: 8px;
|
||
font-size: 12px;
|
||
color: #666666;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.tip-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.recharge-popup {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 999;
|
||
}
|
||
|
||
.popup-mask {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
.popup-content {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background-color: #ffffff;
|
||
border-top-left-radius: 15px;
|
||
border-top-right-radius: 15px;
|
||
padding: 20px;
|
||
}
|
||
|
||
.popup-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 1px solid #e5e5e5;
|
||
}
|
||
|
||
.popup-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.popup-close {
|
||
font-size: 24px;
|
||
color: #999999;
|
||
padding: 5px;
|
||
}
|
||
|
||
.popup-body {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.amount-label {
|
||
/* display: block; REMOVED */
|
||
font-size: 14px;
|
||
color: #333333;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.amount-input {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding: 10px;
|
||
border: 1px solid #e5e5e5;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.currency-symbol {
|
||
font-size: 20px;
|
||
color: #333333;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.amount-field {
|
||
flex: 1;
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.quick-amounts {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
/* gap: 10px; REMOVED */
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.quick-amount {
|
||
padding: 8px 15px;
|
||
border: 1px solid #e5e5e5;
|
||
border-radius: 15px;
|
||
font-size: 14px;
|
||
color: #333333;
|
||
margin-right: 10px; /* REPLACED gap */
|
||
margin-bottom: 10px; /* REPLACED gap */
|
||
}
|
||
|
||
.quick-amount.active {
|
||
background-color: #007aff;
|
||
color: #ffffff;
|
||
border-color: #007aff;
|
||
}
|
||
|
||
.recharge-tip {
|
||
/* display: block; REMOVED */
|
||
font-size: 12px;
|
||
color: #999999;
|
||
}
|
||
|
||
.popup-footer {
|
||
display: flex;
|
||
flex-direction: row;
|
||
/* gap: 15px; REMOVED */
|
||
}
|
||
|
||
.cancel-btn,
|
||
.confirm-btn {
|
||
flex: 1;
|
||
height: 45px;
|
||
border-radius: 22.5px;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
border: none;
|
||
}
|
||
|
||
.cancel-btn {
|
||
background-color: #f5f5f5;
|
||
color: #666666;
|
||
margin-right: 15px; /* REPLACED gap */
|
||
}
|
||
|
||
.confirm-btn {
|
||
background-color: #007aff;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.confirm-btn.disabled {
|
||
background-color: #cccccc;
|
||
opacity: 0.6;
|
||
}
|
||
</style> |