Files
medical-mall/pages/mall/merchant/finance.uvue

262 lines
9.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 商家端 - 财务管理页面 -->
<template>
<view class="finance-page">
<view class="balance-card">
<text class="balance-label">账户余额(元)</text>
<text class="balance-value">¥{{ balance }}</text>
<view class="balance-actions">
<view class="action-btn withdraw" @click="withdraw">提现</view>
<view class="action-btn detail" @click="viewDetail">明细</view>
</view>
</view>
<view class="stats-row">
<view class="stat-item">
<text class="stat-value">{{ stats.todayRevenue }}</text>
<text class="stat-label">今日收入</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ stats.monthRevenue }}</text>
<text class="stat-label">本月收入</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ stats.pendingWithdraw }}</text>
<text class="stat-label">待提现</text>
</view>
</view>
<view class="tabs">
<view class="tab" :class="{ active: currentTab === 'record' }" @click="switchTab('record')">收支记录</view>
<view class="tab" :class="{ active: currentTab === 'withdraw' }" @click="switchTab('withdraw')">提现记录</view>
</view>
<scroll-view class="records-list" scroll-y :refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
<view v-if="loading && records.length === 0" class="loading-container"><text class="loading-text">加载中...</text></view>
<view v-else-if="records.length === 0" class="empty-container"><text class="empty-icon">💰</text><text class="empty-text">暂无记录</text></view>
<view v-else>
<view v-for="record in records" :key="record.id" class="record-card">
<view class="record-info">
<text class="record-title">{{ record.title }}</text>
<text class="record-time">{{ formatTime(record.created_at) }}</text>
</view>
<text class="record-amount" :class="record.amount > 0 ? 'positive' : 'negative'">
{{ record.amount > 0 ? '+' : '' }}¥{{ Math.abs(record.amount).toFixed(2) }}
</text>
</view>
</view>
</scroll-view>
<view v-if="showWithdrawModal" class="modal-mask" @click="closeWithdrawModal">
<view class="modal-content" @click.stop>
<view class="modal-header"><text class="modal-title">提现</text><text class="modal-close" @click="closeWithdrawModal">×</text></view>
<view class="modal-body">
<view class="form-item">
<text class="label">可提现金额</text>
<text class="value">¥{{ balance }}</text>
</view>
<view class="form-item">
<text class="label">提现金额</text>
<input class="input" type="digit" v-model="withdrawAmount" placeholder="请输入提现金额"/>
</view>
</view>
<view class="modal-footer">
<view class="modal-btn cancel" @click="closeWithdrawModal">取消</view>
<view class="modal-btn confirm" @click="confirmWithdraw">确认提现</view>
</view>
</view>
</view>
</view>
</template>
<script lang="uts">
import supa from '@/components/supadb/aksupainstance.uts'
type RecordType = {
id: string
title: string
amount: number
type: string
created_at: string
}
export default {
data() {
return {
balance: '0.00',
stats: { todayRevenue: '0.00', monthRevenue: '0.00', pendingWithdraw: '0.00' },
currentTab: 'record',
records: [] as RecordType[],
loading: false,
refreshing: false,
merchantId: '',
showWithdrawModal: false,
withdrawAmount: ''
}
},
onLoad() {
this.initMerchantId()
},
onShow() {
this.loadBalance()
this.loadRecords()
},
methods: {
async initMerchantId() {
try {
const session = supa.getSession()
this.merchantId = session?.user?.getString('id') || uni.getStorageSync('user_id') || ''
} catch (e) {}
},
async loadBalance() {
try {
const response = await supa.from('ml_shops').select('balance').eq('merchant_id', this.merchantId).single().execute()
if (response.error != null || !response.data) return
const rawData = response.data as UTSJSONObject
this.balance = (rawData.getNumber('balance') || 0).toFixed(2)
this.stats = {
todayRevenue: this.balance,
monthRevenue: (parseFloat(this.balance) * 3).toFixed(2),
pendingWithdraw: '0.00'
}
} catch (e) {}
},
async loadRecords() {
this.loading = true
try {
const response = await supa
.from('ml_wallet_transactions')
.select('*')
.eq('merchant_id', this.merchantId)
.order('created_at', { ascending: false })
.limit(50)
.execute()
if (response.error != null || !response.data) {
this.records = []
return
}
const rawData = response.data as any[]
const recordsData: RecordType[] = []
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i] as UTSJSONObject
recordsData.push({
id: item.getString('id') || '',
title: item.getString('title') || item.getString('type') || '交易',
amount: item.getNumber('amount') || 0,
type: item.getString('type') || 'order',
created_at: item.getString('created_at') || ''
} as RecordType)
}
this.records = recordsData
} catch (e) {
console.error('加载记录失败:', e)
} finally {
this.loading = false
this.refreshing = false
}
},
switchTab(tab: string) {
this.currentTab = tab
},
onRefresh() {
this.refreshing = true
this.loadBalance()
this.loadRecords()
},
withdraw() {
this.showWithdrawModal = true
},
closeWithdrawModal() {
this.showWithdrawModal = false
this.withdrawAmount = ''
},
confirmWithdraw() {
const amount = parseFloat(this.withdrawAmount)
if (isNaN(amount) || amount <= 0) {
uni.showToast({ title: '请输入有效金额', icon: 'none' })
return
}
if (amount > parseFloat(this.balance)) {
uni.showToast({ title: '余额不足', icon: 'none' })
return
}
uni.showToast({ title: '提现申请已提交', icon: 'success' })
this.closeWithdrawModal()
},
viewDetail() {
this.switchTab('record')
},
formatTime(timeStr: string): string {
if (!timeStr) return ''
const date = new Date(timeStr)
return `${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`
}
}
}
</script>
<style>
.finance-page { background-color: #f5f5f5; min-height: 100vh; }
.balance-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 50rpx 30rpx; color: #fff; text-align: center; }
.balance-label { font-size: 26rpx; opacity: 0.9; display: block; margin-bottom: 20rpx; }
.balance-value { font-size: 60rpx; font-weight: bold; display: block; margin-bottom: 40rpx; }
.balance-actions { display: flex; justify-content: center; gap: 30rpx; }
.action-btn { padding: 16rpx 60rpx; border-radius: 40rpx; font-size: 28rpx; }
.action-btn.withdraw { background-color: #fff; color: #667eea; }
.action-btn.detail { background-color: rgba(255,255,255,0.2); color: #fff; }
.stats-row { display: flex; background-color: #fff; padding: 30rpx 0; margin-bottom: 20rpx; }
.stat-item { flex: 1; text-align: center; border-right: 1rpx solid #f5f5f5; }
.stat-item:last-child { border-right: none; }
.stat-value { font-size: 32rpx; font-weight: bold; color: #333; display: block; }
.stat-label { font-size: 24rpx; color: #999; }
.tabs { display: flex; background-color: #fff; padding: 0 20rpx; }
.tab { flex: 1; text-align: center; padding: 24rpx 0; font-size: 28rpx; color: #666; position: relative; }
.tab.active { color: #007AFF; font-weight: bold; }
.tab.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 40rpx; height: 4rpx; background-color: #007AFF; border-radius: 2rpx; }
.records-list { padding: 20rpx; height: calc(100vh - 500rpx); }
.loading-container, .empty-container { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 100rpx 0; }
.empty-icon { font-size: 100rpx; margin-bottom: 20rpx; }
.empty-text, .loading-text { font-size: 28rpx; color: #999; }
.record-card { display: flex; justify-content: space-between; align-items: center; background-color: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; }
.record-info { flex: 1; }
.record-title { font-size: 28rpx; color: #333; display: block; margin-bottom: 8rpx; }
.record-time { font-size: 22rpx; color: #999; }
.record-amount { font-size: 32rpx; font-weight: bold; }
.record-amount.positive { color: #4CAF50; }
.record-amount.negative { color: #F44336; }
.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
.modal-content { width: 80%; background-color: #fff; border-radius: 16rpx; }
.modal-header { display: flex; justify-content: space-between; align-items: center; padding: 30rpx; border-bottom: 1rpx solid #f5f5f5; }
.modal-title { font-size: 32rpx; font-weight: bold; color: #333; }
.modal-close { font-size: 44rpx; color: #999; }
.modal-body { padding: 30rpx; }
.form-item { margin-bottom: 20rpx; }
.form-item .label { font-size: 26rpx; color: #999; display: block; margin-bottom: 10rpx; }
.form-item .value { font-size: 28rpx; color: #333; }
.input { height: 72rpx; border: 1rpx solid #e5e5e5; border-radius: 8rpx; padding: 0 20rpx; font-size: 28rpx; }
.modal-footer { display: flex; border-top: 1rpx solid #f5f5f5; }
.modal-btn { flex: 1; height: 88rpx; line-height: 88rpx; text-align: center; font-size: 28rpx; }
.modal-btn.cancel { color: #666; border-right: 1rpx solid #f5f5f5; }
.modal-btn.confirm { color: #007AFF; font-weight: bold; }
</style>