feat(user): 新增收货地址管理及售后申请页面

- 新增地址类型定义,增强前端地址数据结构
- 新增地址编辑页面,支持地址智能识别和定位选点功能
- 地址编辑支持省市区选择及默认地址设置
- 新增地址列表页面,支持地址展示、删除、编辑和选择功能
- 实现售后申请页面,支持选择售后类型和退款原因
- 售后申请支持商品选择、退款金额计算和凭证上传
- 新增售后详情页面,支持售后状态展示及申请取消
- 优化页面加载和用户交互体验,增加错误提示和权限处理
This commit is contained in:
2026-07-01 12:11:56 +08:00
parent bf6ed504cc
commit 1fa58040f3
636 changed files with 58878 additions and 716 deletions

View File

@@ -0,0 +1,261 @@
import { View, Text, Input } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useState, useEffect } from 'react';
import { getWithdrawConfig, getWithdrawStats, applyWithdraw } from '@/api/shop/shopWithdrawRecord';
import PayModal from '@/components/business/PayModal';
import './index.scss';
definePageConfig({
navigationBarTitleText: '提现',
});
export default function WithdrawPage() {
const [config, setConfig] = useState<any>({});
const [stats, setStats] = useState<any>({});
const [amount, setAmount] = useState('');
const [showPayModal, setShowPayModal] = useState(false);
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
loadConfig();
loadStats();
}, []);
const loadConfig = async () => {
try {
const res: any = await getWithdrawConfig();
if (res.code === 200 || res.code === 0) {
setConfig(res.data || {});
}
} catch (error) {
console.error('加载配置失败', error);
}
};
const loadStats = async () => {
try {
const res: any = await getWithdrawStats();
if (res.code === 200 || res.code === 0) {
setStats(res.data || {});
}
} catch (error) {
console.error('加载统计失败', error);
}
};
// 从配置或默认值读取
const minAmount = config.withdraw_min_amount || '10';
const maxAmount = config.withdraw_max_amount || '200';
const dailyLimit = config.withdraw_daily_limit || '2000';
const dailyCount = config.withdraw_daily_count || 3;
const timeStart = config.withdraw_time_start || '';
const timeEnd = config.withdraw_time_end || '';
const feeRate = config.withdraw_fee_rate || '0';
const arrivalTime = config.arrival_time || '';
const handleWithdraw = () => {
if (!amount || parseFloat(amount) <= 0) {
Taro.showToast({ title: '请输入提现金额', icon: 'none' });
return;
}
const amountNum = parseFloat(amount);
if (amountNum < parseFloat(minAmount)) {
Taro.showToast({ title: `最低提现金额为${minAmount}`, icon: 'none' });
return;
}
if (amountNum > parseFloat(maxAmount)) {
Taro.showToast({ title: `单次最大提现金额为${maxAmount}`, icon: 'none' });
return;
}
setShowPayModal(true);
};
const handlePayConfirm = async (payType: number) => {
setShowPayModal(false);
if (payType !== 0) return;
setSubmitting(true);
try {
const res: any = await applyWithdraw({ amount: parseFloat(amount) });
if (res.code === 200 || res.code === 0) {
Taro.showToast({ title: '提现申请已提交', icon: 'success' });
setTimeout(() => {
Taro.navigateBack();
}, 1500);
} else {
Taro.showToast({ title: res.message || '提交失败', icon: 'none' });
}
} catch (error) {
console.error('提现申请失败', error);
Taro.showToast({ title: '提交失败', icon: 'none' });
} finally {
setSubmitting(false);
}
};
const handleAllWithdraw = () => {
const available = stats.withdrawingTotal || stats.balance || '0.00';
setAmount(available);
};
const availableBalance = stats.withdrawingTotal || stats.balance || '0.00';
return (
<View className="withdraw-page">
{/* 余额卡片 */}
<View className="balance-card">
<View className="balance-header">
<Text className="balance-label"></Text>
</View>
<View className="balance-amount-row">
<Text className="currency-symbol-lg">¥</Text>
<Text className="balance-number">{availableBalance}</Text>
</View>
<View className="balance-tips">
<Text className="balance-tip-text"></Text>
</View>
</View>
{/* 提现输入 */}
<View className="withdraw-card">
<View className="card-title"></View>
<View className="amount-input-wrap">
<Text className="currency-symbol">¥</Text>
<Input
className="amount-input"
type="digit"
placeholder={`最低 ${minAmount}`}
value={amount}
onInput={(e) => setAmount(e.detail.value)}
/>
<Text
className="all-withdraw"
onClick={handleAllWithdraw}
>
</Text>
</View>
{feeRate && parseFloat(feeRate) > 0 ? (
<View className="fee-tips">
<Text className="fee-icon"></Text>
<Text> {feeRate}% </Text>
</View>
) : (
<View className="fee-tips fee-free">
<Text className="fee-icon"></Text>
<Text></Text>
</View>
)}
</View>
{/* ====== 提现规则展示(核心审核项)====== */}
<View className="rules-section">
{/* 标题栏 */}
<View className="rules-header">
<Text className="rules-header-icon">📋</Text>
<Text className="rules-header-title"></Text>
</View>
{/* 规则网格 - 两列布局更醒目 */}
<View className="rules-grid">
<View className="rule-cell">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">{minAmount} </Text>
</View>
<View className="rule-cell">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">{maxAmount} /</Text>
</View>
<View className="rule-cell">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">{dailyLimit} /</Text>
</View>
<View className="rule-cell">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">{dailyCount} /</Text>
</View>
<View className="rule-cell">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">{timeStart && timeEnd ? `${timeStart}-${timeEnd}` : '全天可提'}</Text>
</View>
<View className="rule-cell">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">{arrivalTime || '1-3个工作日'}</Text>
</View>
<View className="rule-cell rule-cell-wide">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">
{feeRate && parseFloat(feeRate) > 0 ? `${feeRate}%` : '免手续费'}
</Text>
</View>
</View>
{/* 详细规则文字 */}
<View className="rules-detail">
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"> ¥{minAmount} ~ ¥{maxAmount}</Text>
</View>
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"> {dailyCount} ¥{dailyLimit}</Text>
</View>
{timeStart && timeEnd ? (
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"> {timeStart} {timeEnd} </Text>
</View>
) : (
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"> 24 </Text>
</View>
)}
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"> {arrivalTime || '1-3个工作日'} </Text>
</View>
{feeRate && parseFloat(feeRate) > 0 ? (
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"> {feeRate}% </Text>
</View>
) : (
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"></Text>
</View>
)}
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text">退</Text>
</View>
</View>
</View>
{/* 底部按钮 */}
<View className="bottom-area">
<View
className={`submit-btn ${amount && !submitting ? '' : 'disabled'}`}
onClick={handleWithdraw}
>
{submitting ? '提交中...' : '确认提现'}
</View>
<View className="submit-disclaimer">
<Text className="disclaimer-text"></Text>
</View>
</View>
<PayModal
visible={showPayModal}
amount={parseFloat(amount) || 0}
onConfirm={handlePayConfirm}
onClose={() => setShowPayModal(false)}
/>
</View>
);
}