feat(withdraw): 添加实名认证验证功能

- 在提现页面集成实名认证状态检查
- 添加 fetchVerifyStatus 函数用于获取认证状态
- 实现认证状态包括未知、已认证、未认证、审核中、已驳回
- 在提交提现前验证用户是否已完成实名认证
- 添加去认证按钮跳转到认证页面
- 优化订单详情和订单列表中的取消订单逻辑
- 修复用户认证页面的表单验证逻辑
- 添加真实姓名和身份证号输入字段到企业认证表单
This commit is contained in:
2026-02-07 15:35:23 +08:00
parent 5581493772
commit 6c83f6c082
4 changed files with 153 additions and 15 deletions

View File

@@ -16,6 +16,8 @@ import {Wallet} from '@nutui/icons-react-taro'
import {businessGradients} from '@/styles/gradients' import {businessGradients} from '@/styles/gradients'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import {useDealerUser} from '@/hooks/useDealerUser' import {useDealerUser} from '@/hooks/useDealerUser'
import {myUserVerify} from '@/api/system/userVerify'
import {goTo} from '@/utils/navigation'
import { import {
pageShopDealerWithdraw, pageShopDealerWithdraw,
addShopDealerWithdraw, addShopDealerWithdraw,
@@ -106,6 +108,8 @@ const DealerWithdraw: React.FC = () => {
const formRef = useRef<any>(null) const formRef = useRef<any>(null)
const {dealerUser} = useDealerUser() const {dealerUser} = useDealerUser()
const [verifyStatus, setVerifyStatus] = useState<'unknown' | 'verified' | 'unverified' | 'pending' | 'rejected'>('unknown')
const [verifyStatusText, setVerifyStatusText] = useState<string>('')
// Tab 切换处理函数 // Tab 切换处理函数
const handleTabChange = (value: string | number) => { const handleTabChange = (value: string | number) => {
@@ -185,6 +189,57 @@ const DealerWithdraw: React.FC = () => {
} }
}, [fetchBalance, fetchWithdrawRecords]) }, [fetchBalance, fetchWithdrawRecords])
// 判断实名认证状态:提现前必须完成实名认证(已通过)
const fetchVerifyStatus = useCallback(async () => {
// Fast path: some pages store this flag after login.
if (String(Taro.getStorageSync('Certification')) === '1') {
setVerifyStatus('verified')
setVerifyStatusText('已实名认证')
return
}
try {
const r = await myUserVerify({})
if (!r) {
setVerifyStatus('unverified')
setVerifyStatusText('未实名认证')
return
}
const s = Number((r as any).status)
const st = String((r as any).statusText || '')
// Common convention in this project: 0审核中/待审核, 1已通过, 2已驳回
if (s === 1) {
setVerifyStatus('verified')
setVerifyStatusText(st || '已实名认证')
return
}
if (s === 0) {
setVerifyStatus('pending')
setVerifyStatusText(st || '审核中')
return
}
if (s === 2) {
setVerifyStatus('rejected')
setVerifyStatusText(st || '已驳回')
return
}
setVerifyStatus('unverified')
setVerifyStatusText(st || '未实名认证')
} catch (e) {
console.warn('获取实名认证状态失败,将按未认证处理:', e)
setVerifyStatus('unverified')
setVerifyStatusText('未实名认证')
}
}, [])
useEffect(() => {
if (!dealerUser?.userId) return
fetchVerifyStatus().then()
}, [dealerUser?.userId, fetchVerifyStatus])
const getStatusText = (status?: number) => { const getStatusText = (status?: number) => {
switch (status) { switch (status) {
case 40: case 40:
@@ -224,6 +279,14 @@ const DealerWithdraw: React.FC = () => {
return return
} }
if (verifyStatus !== 'verified') {
Taro.showToast({
title: '请先完成实名认证',
icon: 'none'
})
return
}
// 验证提现金额 // 验证提现金额
const amount = parseFloat(String(values.amount)) const amount = parseFloat(String(values.amount))
const available = parseFloat(normalizeMoneyString(availableAmount).replace(/,/g, '')) const available = parseFloat(normalizeMoneyString(availableAmount).replace(/,/g, ''))
@@ -366,8 +429,28 @@ const DealerWithdraw: React.FC = () => {
return Number.isFinite(n) ? n.toFixed(2) : '0.00' return Number.isFinite(n) ? n.toFixed(2) : '0.00'
} }
const goVerify = () => {
goTo('/user/userVerify/index')
}
const renderWithdrawForm = () => ( const renderWithdrawForm = () => (
<View> <View>
{(verifyStatus === 'unverified' || verifyStatus === 'pending' || verifyStatus === 'rejected') && (
<View className="rounded-lg bg-white px-4 py-3 mb-4 mx-4">
<View className="flex items-center justify-between">
<View className="flex flex-col">
<Text className="text-sm text-red-500"></Text>
{verifyStatusText ? (
<Text className="text-xs text-gray-500 mt-1">{verifyStatusText}</Text>
) : null}
</View>
<Text className="text-sm text-blue-600" onClick={goVerify}>
</Text>
</View>
</View>
)}
{/* 余额卡片 */} {/* 余额卡片 */}
<View className="rounded-xl p-6 mb-6 text-white relative overflow-hidden" style={{ <View className="rounded-xl p-6 mb-6 text-white relative overflow-hidden" style={{
background: businessGradients.dealer.header background: businessGradients.dealer.header
@@ -452,7 +535,7 @@ const DealerWithdraw: React.FC = () => {
type="primary" type="primary"
nativeType="submit" nativeType="submit"
loading={submitting} loading={submitting}
disabled={submitting} disabled={submitting || verifyStatus !== 'verified'}
> >
{submitting ? '提交中...' : '申请提现'} {submitting ? '提交中...' : '申请提现'}
</Button> </Button>

View File

@@ -20,11 +20,13 @@ const OrderDetail = () => {
// 处理支付超时 // 处理支付超时
const handlePaymentExpired = async () => { const handlePaymentExpired = async () => {
if (!order) return; if (!order) return;
if (!order.orderId) return;
try { try {
// 自动取消过期订单 // 自动取消过期订单
await updateShopOrder({ await updateShopOrder({
...order, // 只传最小字段,避免误取消/误走售后流程
orderId: order.orderId,
orderStatus: 2 // 已取消 orderStatus: 2 // 已取消
}); });

View File

@@ -362,13 +362,23 @@ function OrderList(props: OrderListProps) {
// 确认取消订单 // 确认取消订单
const handleConfirmCancel = async () => { const handleConfirmCancel = async () => {
if (!orderToCancel) return; if (!orderToCancel) return;
if (!orderToCancel.orderId) {
Taro.showToast({
title: '订单信息错误',
icon: 'error'
});
setOrderToCancel(null);
setCancelDialogVisible(false);
return;
}
try { try {
setCancelDialogVisible(false); setCancelDialogVisible(false);
// 更新订单状态为已取消,而不是删除订单 // 更新订单状态为已取消,而不是删除订单
await updateShopOrder({ await updateShopOrder({
...orderToCancel, // 只传最小字段,避免误取消/误走售后流程
orderId: orderToCancel.orderId,
orderStatus: 2 // 已取消 orderStatus: 2 // 已取消
}); });

View File

@@ -53,17 +53,17 @@ function Index() {
const submitSucceed = (values: any) => { const submitSucceed = (values: any) => {
console.log('提交表单', values); console.log('提交表单', values);
if (FormData.status != 2 && FormData.status != undefined) return false; if (FormData.status != 2 && FormData.status != undefined) return false;
if (FormData.type == 0) { if (FormData.type == 0 || FormData.type == 1) {
if (!FormData.sfz1 || !FormData.sfz2) { if (!FormData.realName || !FormData.idCard) {
Taro.showToast({ Taro.showToast({
title: '请上传身份证正反面', title: '请填写真实姓名和身份证号码',
icon: 'none' icon: 'none'
}); });
return false; return false;
} }
if (!FormData.realName || !FormData.idCard) { if (!FormData.sfz1 || !FormData.sfz2) {
Taro.showToast({ Taro.showToast({
title: '请填写真实姓名和身份证号码', title: '请上传身份证正反面',
icon: 'none' icon: 'none'
}); });
return false; return false;
@@ -85,13 +85,6 @@ function Index() {
return false; return false;
} }
} }
if(!FormData.realName){
Taro.showToast({
title: '请填写真实姓名',
icon: 'none'
});
return false;
}
const saveOrUpdate = isUpdate ? updateUserVerify : addUserVerify; const saveOrUpdate = isUpdate ? updateUserVerify : addUserVerify;
saveOrUpdate({...FormData, status: 0}).then(() => { saveOrUpdate({...FormData, status: 0}).then(() => {
Taro.showToast({title: `提交成功`, icon: 'success'}) Taro.showToast({title: `提交成功`, icon: 'success'})
@@ -249,6 +242,54 @@ function Index() {
// 企业类型 // 企业类型
FormData.type == 1 && ( FormData.type == 1 && (
<> <>
<Form.Item
label={'真实姓名'}
name="realName"
required
initialValue={FormData.realName}
rules={[{message: '请输入真实姓名'}]}
>
<Input
placeholder={'请输入真实姓名'}
type="text"
disabled={FormData.status != 2 && FormData.status != undefined}
value={FormData?.realName}
onChange={(value) => setFormData({...FormData, realName: value})}
/>
</Form.Item>
<Form.Item
label={'身份证号码'}
name="idCard"
required
initialValue={FormData.idCard}
rules={[{message: '请输入身份证号码'}]}
>
<Input
placeholder="请输入身份证号码"
type="text"
value={FormData?.idCard}
disabled={FormData.status != 2 && FormData.status != undefined}
maxLength={18}
onChange={(value) => setFormData({...FormData, idCard: value})}
/>
</Form.Item>
<Form.Item
label={'上传证件'}
name="image"
required
rules={[{message: '请上传身份证正反面'}]}
>
<div className={'flex gap-2'}>
<div onClick={uploadSfz1}>
<Image src={FormData.sfz1} lazyLoad={false}
radius="10%" width="80" height="80"/>
</div>
<div onClick={uploadSfz2}>
<Image src={FormData.sfz2} mode={'scaleToFill'} lazyLoad={false}
radius="10%" width="80" height="80"/>
</div>
</div>
</Form.Item>
<Form.Item <Form.Item
label={'主体名称'} label={'主体名称'}
name="name" name="name"
@@ -260,6 +301,7 @@ function Index() {
placeholder={'请输入主体名称'} placeholder={'请输入主体名称'}
type="text" type="text"
value={FormData?.name} value={FormData?.name}
disabled={FormData.status != 2 && FormData.status != undefined}
onChange={(value) => setFormData({...FormData, name: value})} onChange={(value) => setFormData({...FormData, name: value})}
/> />
</Form.Item> </Form.Item>
@@ -274,6 +316,7 @@ function Index() {
placeholder="请输入营业执照号码" placeholder="请输入营业执照号码"
type="text" type="text"
value={FormData?.zzCode} value={FormData?.zzCode}
disabled={FormData.status != 2 && FormData.status != undefined}
maxLength={18} maxLength={18}
onChange={(value) => setFormData({...FormData, zzCode: value})} onChange={(value) => setFormData({...FormData, zzCode: value})}
/> />