Files
medical-mall/pages/mall/merchant/finance.uvue
2026-04-13 11:32:31 +08:00

289 lines
11 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">
<!-- #ifdef MP-WEIXIN -->
<view style="padding-top: var(--status-bar-height); background-color: #ffffff; display: flex; flex-direction: row; align-items: flex-end; border-bottom: 1rpx solid #eeeeee; box-sizing: border-box; height: calc(88rpx + var(--status-bar-height));">
<view style="display: flex; flex-direction: row; align-items: center; padding: 0 30rpx; height: 88rpx;" @click="uni.navigateBack()">
<text style="font-size: 44rpx; color: #333333; line-height: 1; margin-right: 6rpx;"></text>
<text style="font-size: 28rpx; color: #333333;">返回</text>
</view>
</view>
<!-- #endif -->
<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'
import { USE_MOCK, MOCK_MERCHANT_ID, MOCK_BALANCE, MOCK_FINANCE_STATS, getMockFinanceRecords } from '@/pages/mall/merchant/mock/merchant-mock-data.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() {
if (USE_MOCK) {
this.merchantId = MOCK_MERCHANT_ID
return
}
try {
const session = supa.getSession()
this.merchantId = session?.user?.getString('id') || uni.getStorageSync('user_id') || ''
} catch (e) {}
},
async loadBalance() {
if (USE_MOCK) {
this.balance = MOCK_BALANCE
this.stats = MOCK_FINANCE_STATS
return
}
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
if (USE_MOCK) {
this.records = getMockFinanceRecords(this.currentTab) as RecordType[]
this.loading = false
this.refreshing = false
return
}
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, #A6F1E4 0%, #69DFC2 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; flex-direction: row; justify-content: center; gap: 30rpx; }
.action-btn { padding: 16rpx 60rpx; border-radius: 40rpx; font-size: 28rpx; }
.action-btn.withdraw { background-color: #fff; color: #09C39D; }
.action-btn.detail { background-color: rgba(255,255,255,0.2); color: #fff; }
.stats-row { display: flex;
flex-direction: row;
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;
flex-direction: row;
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: #09C39D; font-weight: bold; }
.tab.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 40rpx; height: 4rpx; background-color: #09C39D; 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; flex-direction: row; 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; flex-direction: row; 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; flex-direction: row; 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: #09C39D; font-weight: bold; }
</style>