462 lines
13 KiB
TypeScript
462 lines
13 KiB
TypeScript
import React, {useState, useEffect, useCallback} from 'react'
|
||
import {View, Text} from '@tarojs/components'
|
||
import {
|
||
Cell,
|
||
Space,
|
||
Button,
|
||
Input,
|
||
CellGroup,
|
||
Tabs,
|
||
Tag,
|
||
Empty,
|
||
ActionSheet,
|
||
Loading,
|
||
PullToRefresh
|
||
} from '@nutui/nutui-react-taro'
|
||
import {Wallet, ArrowRight} from '@nutui/icons-react-taro'
|
||
import {businessGradients} from '@/styles/gradients'
|
||
import Taro from '@tarojs/taro'
|
||
import {useDealerUser} from '@/hooks/useDealerUser'
|
||
import {pageShopDealerWithdraw, addShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw'
|
||
import type {ShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw/model'
|
||
import {ShopDealerBank} from "@/api/shop/shopDealerBank/model";
|
||
import {listShopDealerBank} from "@/api/shop/shopDealerBank";
|
||
import {listCmsWebsiteField} from "@/api/cms/cmsWebsiteField";
|
||
|
||
interface WithdrawRecordWithDetails extends ShopDealerWithdraw {
|
||
accountDisplay?: string
|
||
}
|
||
|
||
const DealerWithdraw: React.FC = () => {
|
||
const [activeTab, setActiveTab] = useState<string | number>('0')
|
||
const [loading, setLoading] = useState<boolean>(false)
|
||
const [refreshing, setRefreshing] = useState<boolean>(false)
|
||
const [submitting, setSubmitting] = useState<boolean>(false)
|
||
const [banks, setBanks] = useState<any[]>([])
|
||
const [bank, setBank] = useState<ShopDealerBank>()
|
||
const [isVisible, setIsVisible] = useState<boolean>(false)
|
||
const [availableAmount, setAvailableAmount] = useState<string>('0.00')
|
||
const [withdrawRecords, setWithdrawRecords] = useState<WithdrawRecordWithDetails[]>([])
|
||
const [withdrawAmount, setWithdrawAmount] = useState<string>('')
|
||
const [withdrawValue, setWithdrawValue] = useState<string>('')
|
||
|
||
const {dealerUser} = useDealerUser()
|
||
|
||
// Tab 切换处理函数
|
||
const handleTabChange = (value: string | number) => {
|
||
console.log('Tab切换到:', value)
|
||
setActiveTab(value)
|
||
|
||
// 如果切换到提现记录页面,刷新数据
|
||
if (String(value) === '1') {
|
||
fetchWithdrawRecords().then()
|
||
}
|
||
}
|
||
|
||
// 获取可提现余额
|
||
const fetchBalance = useCallback(async () => {
|
||
console.log(dealerUser, 'dealerUser...')
|
||
try {
|
||
setAvailableAmount(String(dealerUser?.money || '0.00'))
|
||
} catch (error) {
|
||
console.error('获取余额失败:', error)
|
||
}
|
||
}, [dealerUser])
|
||
|
||
// 获取提现记录
|
||
const fetchWithdrawRecords = useCallback(async () => {
|
||
if (!dealerUser?.userId) return
|
||
|
||
try {
|
||
setLoading(true)
|
||
const result = await pageShopDealerWithdraw({
|
||
page: 1,
|
||
limit: 100,
|
||
userId: dealerUser.userId
|
||
})
|
||
|
||
if (result?.list) {
|
||
const processedRecords = result.list.map(record => ({
|
||
...record,
|
||
accountDisplay: getAccountDisplay(record)
|
||
}))
|
||
setWithdrawRecords(processedRecords)
|
||
}
|
||
} catch (error) {
|
||
console.error('获取提现记录失败:', error)
|
||
Taro.showToast({
|
||
title: '获取提现记录失败',
|
||
icon: 'error'
|
||
})
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}, [dealerUser?.userId])
|
||
|
||
function fetchBanks() {
|
||
listShopDealerBank({}).then(data => {
|
||
const list = data.map(d => {
|
||
d.name = d.bankName;
|
||
d.type = d.bankName;
|
||
return d;
|
||
})
|
||
setBanks(list.concat({
|
||
name: '管理银行卡',
|
||
type: 'add'
|
||
}))
|
||
setBank(data[0])
|
||
})
|
||
}
|
||
|
||
// 格式化账户显示
|
||
const getAccountDisplay = (record: ShopDealerWithdraw) => {
|
||
if (record.payType === 10) {
|
||
return '微信钱包'
|
||
} else if (record.payType === 20 && record.alipayAccount) {
|
||
return `支付宝(${record.alipayAccount.slice(-4)})`
|
||
} else if (record.payType === 30 && record.bankCard) {
|
||
return `${record.bankName || '银行卡'}(尾号${record.bankCard.slice(-4)})`
|
||
}
|
||
return '未知账户'
|
||
}
|
||
|
||
// 刷新数据
|
||
const handleRefresh = async () => {
|
||
setRefreshing(true)
|
||
await Promise.all([fetchBalance(), fetchWithdrawRecords()])
|
||
setRefreshing(false)
|
||
}
|
||
|
||
const handleSelect = (item: ShopDealerBank) => {
|
||
if(item.type === 'add'){
|
||
return Taro.navigateTo({
|
||
url: '/dealer/bank/index'
|
||
})
|
||
}
|
||
setBank(item)
|
||
setIsVisible(false)
|
||
}
|
||
|
||
function fetchCmsField() {
|
||
listCmsWebsiteField({ name: 'WithdrawValue'}).then(res => {
|
||
if(res && res.length > 0){
|
||
const text = res[0].value;
|
||
setWithdrawValue(text || '')
|
||
}
|
||
})
|
||
}
|
||
|
||
// 初始化加载数据
|
||
useEffect(() => {
|
||
if (dealerUser?.userId) {
|
||
fetchBalance().then()
|
||
fetchWithdrawRecords().then()
|
||
fetchBanks()
|
||
fetchCmsField()
|
||
}
|
||
}, [fetchBalance, fetchWithdrawRecords])
|
||
|
||
const getStatusText = (status?: number) => {
|
||
switch (status) {
|
||
case 40:
|
||
return '已到账'
|
||
case 20:
|
||
return '审核通过'
|
||
case 10:
|
||
return '待审核'
|
||
case 30:
|
||
return '已驳回'
|
||
default:
|
||
return '未知'
|
||
}
|
||
}
|
||
|
||
const getStatusColor = (status?: number) => {
|
||
switch (status) {
|
||
case 40:
|
||
return 'success'
|
||
case 20:
|
||
return 'success'
|
||
case 10:
|
||
return 'warning'
|
||
case 30:
|
||
return 'danger'
|
||
default:
|
||
return 'default'
|
||
}
|
||
}
|
||
|
||
const handleSubmit = async () => {
|
||
if (!dealerUser?.userId) {
|
||
Taro.showToast({
|
||
title: '用户信息获取失败',
|
||
icon: 'error'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (!bank) {
|
||
Taro.showToast({
|
||
title: '请选择提现银行卡',
|
||
icon: 'error'
|
||
})
|
||
return
|
||
}
|
||
|
||
// 验证提现金额
|
||
const amount = parseFloat(withdrawAmount)
|
||
const availableStr = String(availableAmount || '0')
|
||
const available = parseFloat(availableStr.replace(/,/g, ''))
|
||
|
||
if (isNaN(amount) || amount <= 0) {
|
||
Taro.showToast({
|
||
title: '请输入有效的提现金额',
|
||
icon: 'error'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (amount < 100) {
|
||
Taro.showToast({
|
||
title: '最低提现金额为100元',
|
||
icon: 'error'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (amount > available) {
|
||
Taro.showToast({
|
||
title: '提现金额超过可用余额',
|
||
icon: 'error'
|
||
})
|
||
return
|
||
}
|
||
|
||
// 验证银行卡信息
|
||
if (!bank.bankCard || !bank.bankAccount || !bank.bankName) {
|
||
Taro.showToast({
|
||
title: '银行卡信息不完整',
|
||
icon: 'error'
|
||
})
|
||
return
|
||
}
|
||
|
||
try {
|
||
setSubmitting(true)
|
||
|
||
const withdrawData: ShopDealerWithdraw = {
|
||
userId: dealerUser.userId,
|
||
money: withdrawAmount,
|
||
payType: 30, // 银行卡提现
|
||
applyStatus: 10, // 待审核
|
||
platform: 'MiniProgram',
|
||
bankCard: bank.bankCard,
|
||
bankAccount: bank.bankAccount,
|
||
bankName: bank.bankName
|
||
}
|
||
|
||
await addShopDealerWithdraw(withdrawData)
|
||
|
||
Taro.showToast({
|
||
title: '提现申请已提交',
|
||
icon: 'success'
|
||
})
|
||
|
||
// 重置表单
|
||
setWithdrawAmount('')
|
||
|
||
// 刷新数据
|
||
await handleRefresh()
|
||
|
||
// 切换到提现记录页面
|
||
setActiveTab('1')
|
||
|
||
} catch (error: any) {
|
||
console.error('提现申请失败:', error)
|
||
Taro.showToast({
|
||
title: error.message || '提现申请失败',
|
||
icon: 'error'
|
||
})
|
||
} finally {
|
||
setSubmitting(false)
|
||
}
|
||
}
|
||
|
||
// 格式化金额
|
||
const formatMoney = (money?: string) => {
|
||
if (!money) return '0.00'
|
||
return parseFloat(money).toFixed(2)
|
||
}
|
||
|
||
// 计算预计到账金额
|
||
const calculateExpectedAmount = (amount: string) => {
|
||
if (!amount || isNaN(parseFloat(amount))) return '0.00'
|
||
const withdrawAmount = parseFloat(amount)
|
||
// 提现费率 16% + 3元
|
||
const feeRate = 0.16
|
||
const fixedFee = 3
|
||
const totalFee = withdrawAmount * feeRate + fixedFee
|
||
const expectedAmount = withdrawAmount - totalFee
|
||
return Math.max(0, expectedAmount).toFixed(2)
|
||
}
|
||
|
||
const renderWithdrawForm = () => (
|
||
<View>
|
||
{/* 余额卡片 */}
|
||
<View className="rounded-xl p-6 mb-6 text-white relative overflow-hidden" style={{
|
||
background: businessGradients.dealer.header
|
||
}}>
|
||
{/* 装饰背景 - 小程序兼容版本 */}
|
||
<View className="absolute top-0 right-0 w-24 h-24 rounded-full" style={{
|
||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||
right: '-12px',
|
||
top: '-12px'
|
||
}}></View>
|
||
|
||
<View className="flex items-center justify-between relative z-10">
|
||
<View className={'flex flex-col'}>
|
||
<Text className="text-2xl font-bold text-white">{formatMoney(dealerUser?.money)}</Text>
|
||
<Text className="text-white text-opacity-80 text-sm mb-1">可提现余额</Text>
|
||
</View>
|
||
<View className="p-3 rounded-full" style={{
|
||
background: 'rgba(255, 255, 255, 0.2)'
|
||
}}>
|
||
<Wallet color="white" size="32"/>
|
||
</View>
|
||
</View>
|
||
<View className="mt-4 pt-4 relative z-10" style={{
|
||
borderTop: '1px solid rgba(255, 255, 255, 0.3)'
|
||
}}>
|
||
<Text className="text-white text-opacity-80 text-xs">
|
||
最低提现金额:¥100
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<CellGroup>
|
||
<Cell style={{
|
||
padding: '36px 12px'
|
||
}} title={
|
||
<View className="flex items-center justify-between">
|
||
<Text className={'text-xl'}>¥</Text>
|
||
<Input
|
||
placeholder="提现金额"
|
||
type="number"
|
||
maxLength={7}
|
||
value={withdrawAmount}
|
||
onChange={(value) => setWithdrawAmount(value)}
|
||
style={{
|
||
padding: '0 10px',
|
||
fontSize: '20px'
|
||
}}
|
||
/>
|
||
<Button fill="none" size={'small'} onClick={() => setWithdrawAmount(dealerUser?.money || '0')}><Text className={'text-blue-500'}>全部提现</Text></Button>
|
||
</View>
|
||
}
|
||
/>
|
||
<Cell title={'提现到'} onClick={() => setIsVisible(true)} extra={
|
||
<View className="flex items-center justify-between gap-1">
|
||
{bank ? <Text className={'text-gray-800'}>{bank.bankName}</Text> : <Text className={'text-gray-400'}>请选择</Text>}
|
||
<ArrowRight className={'text-gray-300'} size={15}/>
|
||
</View>
|
||
}/>
|
||
<Cell title={'预计到账金额'} description={'提现费率 16% +3元'} extra={
|
||
<View className="flex items-center justify-between gap-1">
|
||
<Text className={'text-orange-500 px-2 text-lg'}>¥{calculateExpectedAmount(withdrawAmount)}</Text>
|
||
</View>
|
||
}/>
|
||
<Cell title={<Text className={'text-gray-400'}>说明:{withdrawValue}</Text>}/>
|
||
</CellGroup>
|
||
|
||
<View className="mt-6 px-4">
|
||
<Button
|
||
block
|
||
type="primary"
|
||
nativeType="submit"
|
||
loading={submitting}
|
||
disabled={submitting || !withdrawAmount || !bank}
|
||
onClick={handleSubmit}
|
||
>
|
||
{submitting ? '提交中...' : '申请提现'}
|
||
</Button>
|
||
</View>
|
||
</View>
|
||
)
|
||
|
||
const renderWithdrawRecords = () => {
|
||
console.log('渲染提现记录:', {loading, recordsCount: withdrawRecords.length, dealerUserId: dealerUser?.userId})
|
||
|
||
return (
|
||
<PullToRefresh
|
||
disabled={refreshing}
|
||
onRefresh={handleRefresh}
|
||
>
|
||
<View>
|
||
{loading ? (
|
||
<View className="text-center py-8">
|
||
<Loading/>
|
||
<Text className="text-gray-500 mt-2">加载中...</Text>
|
||
</View>
|
||
) : withdrawRecords.length > 0 ? (
|
||
withdrawRecords.map(record => (
|
||
<View key={record.id} className="rounded-lg bg-gray-50 p-4 mb-3 shadow-sm">
|
||
<View className="flex justify-between items-start mb-3">
|
||
<Space direction={'vertical'}>
|
||
<Text className="font-semibold text-gray-800 mb-1">
|
||
提现金额:¥{record.money}
|
||
</Text>
|
||
<Text className="text-sm text-gray-500">
|
||
提现账户:{record.accountDisplay}
|
||
</Text>
|
||
</Space>
|
||
<Tag type={getStatusColor(record.applyStatus)}>
|
||
{getStatusText(record.applyStatus)}
|
||
</Tag>
|
||
</View>
|
||
|
||
<View className="text-xs text-gray-400">
|
||
<Text>申请时间:{record.createTime}</Text>
|
||
{record.auditTime && (
|
||
<Text className="block mt-1">
|
||
审核时间:{new Date(record.auditTime).toLocaleString()}
|
||
</Text>
|
||
)}
|
||
{record.rejectReason && (
|
||
<Text className="block mt-1 text-red-500">
|
||
驳回原因:{record.rejectReason}
|
||
</Text>
|
||
)}
|
||
</View>
|
||
</View>
|
||
))
|
||
) : (
|
||
<Empty description="暂无提现记录"/>
|
||
)}
|
||
</View>
|
||
</PullToRefresh>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<View className="bg-gray-50 min-h-screen">
|
||
<Tabs value={activeTab} onChange={handleTabChange}>
|
||
<Tabs.TabPane title="申请提现" value="0">
|
||
{renderWithdrawForm()}
|
||
</Tabs.TabPane>
|
||
|
||
<Tabs.TabPane title="提现记录" value="1">
|
||
{renderWithdrawRecords()}
|
||
</Tabs.TabPane>
|
||
</Tabs>
|
||
<ActionSheet
|
||
visible={isVisible}
|
||
options={banks}
|
||
onSelect={handleSelect}
|
||
onCancel={() => setIsVisible(false)}
|
||
/>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
export default DealerWithdraw
|