Files
template-10560/src/dealer/withdraw/index.tsx
赵忠林 cb40ed7cb7 feat(user): 新增站内消息功能
- 添加聊天消息相关API和模型定义
- 实现消息列表、消息详情和发送消息页面
- 集成消息功能到首页和团队页面
-优化用户模型,增加别名字段
2025-09-16 17:42:49 +08:00

462 lines
13 KiB
TypeScript
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.

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