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 Taro from '@tarojs/taro'
import {useDealerUser} from '@/hooks/useDealerUser'
import {myUserVerify} from '@/api/system/userVerify'
import {goTo} from '@/utils/navigation'
import {
pageShopDealerWithdraw,
addShopDealerWithdraw,
@@ -106,6 +108,8 @@ const DealerWithdraw: React.FC = () => {
const formRef = useRef<any>(null)
const {dealerUser} = useDealerUser()
const [verifyStatus, setVerifyStatus] = useState<'unknown' | 'verified' | 'unverified' | 'pending' | 'rejected'>('unknown')
const [verifyStatusText, setVerifyStatusText] = useState<string>('')
// Tab 切换处理函数
const handleTabChange = (value: string | number) => {
@@ -185,6 +189,57 @@ const DealerWithdraw: React.FC = () => {
}
}, [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) => {
switch (status) {
case 40:
@@ -224,6 +279,14 @@ const DealerWithdraw: React.FC = () => {
return
}
if (verifyStatus !== 'verified') {
Taro.showToast({
title: '请先完成实名认证',
icon: 'none'
})
return
}
// 验证提现金额
const amount = parseFloat(String(values.amount))
const available = parseFloat(normalizeMoneyString(availableAmount).replace(/,/g, ''))
@@ -366,8 +429,28 @@ const DealerWithdraw: React.FC = () => {
return Number.isFinite(n) ? n.toFixed(2) : '0.00'
}
const goVerify = () => {
goTo('/user/userVerify/index')
}
const renderWithdrawForm = () => (
<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={{
background: businessGradients.dealer.header
@@ -452,7 +535,7 @@ const DealerWithdraw: React.FC = () => {
type="primary"
nativeType="submit"
loading={submitting}
disabled={submitting}
disabled={submitting || verifyStatus !== 'verified'}
>
{submitting ? '提交中...' : '申请提现'}
</Button>

View File

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

View File

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

View File

@@ -53,17 +53,17 @@ function Index() {
const submitSucceed = (values: any) => {
console.log('提交表单', values);
if (FormData.status != 2 && FormData.status != undefined) return false;
if (FormData.type == 0) {
if (!FormData.sfz1 || !FormData.sfz2) {
if (FormData.type == 0 || FormData.type == 1) {
if (!FormData.realName || !FormData.idCard) {
Taro.showToast({
title: '请上传身份证正反面',
title: '请填写真实姓名和身份证号码',
icon: 'none'
});
return false;
}
if (!FormData.realName || !FormData.idCard) {
if (!FormData.sfz1 || !FormData.sfz2) {
Taro.showToast({
title: '请填写真实姓名和身份证号码',
title: '请上传身份证正反面',
icon: 'none'
});
return false;
@@ -85,13 +85,6 @@ function Index() {
return false;
}
}
if(!FormData.realName){
Taro.showToast({
title: '请填写真实姓名',
icon: 'none'
});
return false;
}
const saveOrUpdate = isUpdate ? updateUserVerify : addUserVerify;
saveOrUpdate({...FormData, status: 0}).then(() => {
Taro.showToast({title: `提交成功`, icon: 'success'})
@@ -249,6 +242,54 @@ function Index() {
// 企业类型
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
label={'主体名称'}
name="name"
@@ -260,6 +301,7 @@ function Index() {
placeholder={'请输入主体名称'}
type="text"
value={FormData?.name}
disabled={FormData.status != 2 && FormData.status != undefined}
onChange={(value) => setFormData({...FormData, name: value})}
/>
</Form.Item>
@@ -274,6 +316,7 @@ function Index() {
placeholder="请输入营业执照号码"
type="text"
value={FormData?.zzCode}
disabled={FormData.status != 2 && FormData.status != undefined}
maxLength={18}
onChange={(value) => setFormData({...FormData, zzCode: value})}
/>