feat(payment): 新增线下付款方式及相关订单状态支持
- 支付页面新增线下付款选项,支持微信转账付款方式 - 订单流程新增线下付款待确认(offline_pending)阶段 - 订单详情与订单卡片显示线下付款状态及提示信息 - 支付接口和模型增加线下付款支付方式(payType=9)支持 - 订单列表页区分线下付款待确认与待收款状态 - 地址编辑页修复智能识别按钮无法点击问题,调整布局避免被Textarea遮挡
This commit is contained in:
@@ -292,3 +292,14 @@ adapter `/list` 接口忽略分页,一次返回全部收藏;前端 `listShop
|
|||||||
- 删除了 `handleContactService` 函数和跳转到 `/pages/user/customer-service/index` 的逻辑
|
- 删除了 `handleContactService` 函数和跳转到 `/pages/user/customer-service/index` 的逻辑
|
||||||
- **注意:`open-type="contact"` 必须真机预览测试,开发者工具中点击无反应**
|
- **注意:`open-type="contact"` 必须真机预览测试,开发者工具中点击无反应**
|
||||||
- `pages/user/customer-service/index` 页面保留但不再从商品详情页跳转
|
- `pages/user/customer-service/index` 页面保留但不再从商品详情页跳转
|
||||||
|
|
||||||
|
## 地址编辑页识别按钮点不中修复(2026-07-14)
|
||||||
|
|
||||||
|
### 问题
|
||||||
|
`pages/user/address-edit` 智能识别区的「识别」按钮点击无反应。
|
||||||
|
|
||||||
|
### 根因
|
||||||
|
按钮使用 `absolute right-2 bottom-2` 定位叠在 `Textarea` 上方。微信小程序中 `Textarea` 是原生组件,层级高于普通 `View`,会遮挡 `absolute` 叠加的按钮,`z-index` 也无效,导致按钮点击事件被 Textarea 拦截。
|
||||||
|
|
||||||
|
### 修改
|
||||||
|
- **`src/pages/user/address-edit.tsx`**:识别区布局从 `relative` + `absolute` 叠加改为 flex 纵向布局——Textarea 在上,识别按钮 `flex justify-end mt-2` 在下方右对齐。去掉 `relative` class,按钮不再叠加在 Textarea 上。
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export interface ShopOrder {
|
|||||||
formId?: number;
|
formId?: number;
|
||||||
// 支付的用户id
|
// 支付的用户id
|
||||||
payUserId?: number;
|
payUserId?: number;
|
||||||
// 支付方式:0余额支付, 1微信支付, 2支付宝, 3银联, 4现金, 5POS机, 6免费, 7积分支付, 8货到付款
|
// 支付方式:0余额支付, 1微信支付, 2支付宝, 3银联, 4现金, 5POS机, 6免费, 7积分支付, 8货到付款, 9线下付款
|
||||||
payType?: number;
|
payType?: number;
|
||||||
// 代付支付方式
|
// 代付支付方式
|
||||||
friendPayType?: number;
|
friendPayType?: number;
|
||||||
@@ -187,7 +187,7 @@ export interface OrderCreateRequest {
|
|||||||
warehouseId?: number;
|
warehouseId?: number;
|
||||||
// 收货地址ID
|
// 收货地址ID
|
||||||
addressId?: number;
|
addressId?: number;
|
||||||
// 支付方式:0余额支付, 1微信支付, 2支付宝, 3银联, 4现金, 5POS机, 6免费, 7积分支付, 8货到付款
|
// 支付方式:0余额支付, 1微信支付, 2支付宝, 3银联, 4现金, 5POS机, 6免费, 7积分支付, 8货到付款, 9线下付款
|
||||||
payType: number;
|
payType: number;
|
||||||
// 支付状态(货到付款由后端自动设置,前端无需传递)
|
// 支付状态(货到付款由后端自动设置,前端无需传递)
|
||||||
payStatus?: boolean;
|
payStatus?: boolean;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ interface OrderCardProps {
|
|||||||
const getCardStatus = (order: ShopOrder): { text: string; color: string } => {
|
const getCardStatus = (order: ShopOrder): { text: string; color: string } => {
|
||||||
const { payStatus, deliveryStatus, orderStatus, payType } = order
|
const { payStatus, deliveryStatus, orderStatus, payType } = order
|
||||||
const isCod = payType === 8 // 货到付款
|
const isCod = payType === 8 // 货到付款
|
||||||
|
const isOffline = payType === 9 // 线下付款
|
||||||
|
|
||||||
if (orderStatus === 2) return { text: '已取消', color: '#999' }
|
if (orderStatus === 2) return { text: '已取消', color: '#999' }
|
||||||
if (orderStatus === 3) return { text: '取消中', color: '#ff7d00' }
|
if (orderStatus === 3) return { text: '取消中', color: '#ff7d00' }
|
||||||
@@ -24,6 +25,8 @@ const getCardStatus = (order: ShopOrder): { text: string; color: string } => {
|
|||||||
// 终态:orderStatus=1 必须最先判断,盖过 deliveryStatus/payStatus
|
// 终态:orderStatus=1 必须最先判断,盖过 deliveryStatus/payStatus
|
||||||
if (orderStatus === 1) return { text: '已完成', color: '#0e932e' }
|
if (orderStatus === 1) return { text: '已完成', color: '#0e932e' }
|
||||||
|
|
||||||
|
// 线下付款·待确认:下单后等待商家确认收款
|
||||||
|
if (isOffline && !payStatus) return { text: '线下付款·待确认', color: '#ff7d00' }
|
||||||
// 货到付款:payStatus=true 直接进入待发货
|
// 货到付款:payStatus=true 直接进入待发货
|
||||||
if (!payStatus && !isCod) return { text: '待付款', color: '#ee0a24' }
|
if (!payStatus && !isCod) return { text: '待付款', color: '#ee0a24' }
|
||||||
if (isCod && deliveryStatus === 10) return { text: '货到付款·待发货', color: '#4b9cf5' }
|
if (isCod && deliveryStatus === 10) return { text: '货到付款·待发货', color: '#4b9cf5' }
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const PAY_TYPE_MAP: Record<number, string> = {
|
|||||||
6: '免费',
|
6: '免费',
|
||||||
7: '积分支付',
|
7: '积分支付',
|
||||||
8: '货到付款',
|
8: '货到付款',
|
||||||
|
9: '线下付款',
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发票状态映射
|
// 发票状态映射
|
||||||
@@ -44,6 +45,7 @@ const DELIVERY_STATUS_MAP: Record<number, string> = {
|
|||||||
const getOrderDisplayStatus = (order: ShopOrder): { title: string; bgColor: string; subtitle: string } => {
|
const getOrderDisplayStatus = (order: ShopOrder): { title: string; bgColor: string; subtitle: string } => {
|
||||||
const { payStatus, deliveryStatus, orderStatus, payType } = order
|
const { payStatus, deliveryStatus, orderStatus, payType } = order
|
||||||
const isCod = payType === 8 // 货到付款
|
const isCod = payType === 8 // 货到付款
|
||||||
|
const isOffline = payType === 9 // 线下付款
|
||||||
|
|
||||||
// 退款/取消相关状态
|
// 退款/取消相关状态
|
||||||
if (orderStatus === OrderStatus.Cancelled) {
|
if (orderStatus === OrderStatus.Cancelled) {
|
||||||
@@ -63,9 +65,12 @@ const getOrderDisplayStatus = (order: ShopOrder): { title: string; bgColor: stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 正常订单流程
|
// 正常订单流程
|
||||||
if (!payStatus && !isCod) {
|
if (!payStatus && !isCod && !isOffline) {
|
||||||
return { bgColor: '#ff7d00', title: '待付款', subtitle: '请尽快完成支付' }
|
return { bgColor: '#ff7d00', title: '待付款', subtitle: '请尽快完成支付' }
|
||||||
}
|
}
|
||||||
|
if (isOffline && !payStatus) {
|
||||||
|
return { bgColor: '#ff7d00', title: '线下付款·待确认', subtitle: '请通过微信转账完成付款,商家确认后发货' }
|
||||||
|
}
|
||||||
if (isCod && !payStatus) {
|
if (isCod && !payStatus) {
|
||||||
return { bgColor: '#4b9cf5', title: '货到付款·待发货', subtitle: '送达时支付' }
|
return { bgColor: '#4b9cf5', title: '货到付款·待发货', subtitle: '送达时支付' }
|
||||||
}
|
}
|
||||||
@@ -87,17 +92,20 @@ const getOrderDisplayStatus = (order: ShopOrder): { title: string; bgColor: stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 根据订单状态判断当前操作阶段 */
|
/** 根据订单状态判断当前操作阶段 */
|
||||||
type OrderPhase = 'unpaid' | 'unshipped' | 'shipped' | 'completed' | 'cancelled' | 'refund'
|
type OrderPhase = 'unpaid' | 'offline_pending' | 'unshipped' | 'shipped' | 'completed' | 'cancelled' | 'refund'
|
||||||
|
|
||||||
const getOrderPhase = (order: ShopOrder): OrderPhase => {
|
const getOrderPhase = (order: ShopOrder): OrderPhase => {
|
||||||
const { payStatus, deliveryStatus, orderStatus, payType } = order
|
const { payStatus, deliveryStatus, orderStatus, payType } = order
|
||||||
const isCod = payType === 8 // 货到付款
|
const isCod = payType === 8 // 货到付款
|
||||||
|
const isOffline = payType === 9 // 线下付款
|
||||||
|
|
||||||
// 退款/取消
|
// 退款/取消
|
||||||
if ([OrderStatus.Cancelled, OrderStatus.Cancelling, OrderStatus.RefundSuccess, OrderStatus.RefundApply, OrderStatus.ClientRefundApply, OrderStatus.RefundRejected].includes(orderStatus as OrderStatus)) {
|
if ([OrderStatus.Cancelled, OrderStatus.Cancelling, OrderStatus.RefundSuccess, OrderStatus.RefundApply, OrderStatus.ClientRefundApply, OrderStatus.RefundRejected].includes(orderStatus as OrderStatus)) {
|
||||||
return orderStatus === OrderStatus.Cancelled || orderStatus === OrderStatus.RefundSuccess ? 'cancelled' : 'refund'
|
return orderStatus === OrderStatus.Cancelled || orderStatus === OrderStatus.RefundSuccess ? 'cancelled' : 'refund'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 线下付款且未确认收款:等待商家确认,不显示"立即支付"按钮
|
||||||
|
if (isOffline && !payStatus) return 'offline_pending'
|
||||||
// 货到付款订单:payStatus=true 但还未实际付款,直接进入 unshipped 阶段(不需要"立即支付"按钮)
|
// 货到付款订单:payStatus=true 但还未实际付款,直接进入 unshipped 阶段(不需要"立即支付"按钮)
|
||||||
if (!payStatus && !isCod) return 'unpaid'
|
if (!payStatus && !isCod) return 'unpaid'
|
||||||
if (deliveryStatus === 10) return 'unshipped'
|
if (deliveryStatus === 10) return 'unshipped'
|
||||||
@@ -445,6 +453,28 @@ const OrderDetailPage: React.FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 线下付款·待确认: 取消 + 联系客服 */}
|
||||||
|
{phase === 'offline_pending' && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
size='small'
|
||||||
|
className='flex-1'
|
||||||
|
style={{ borderColor: '#ddd', color: '#666' }}
|
||||||
|
onClick={handleCancelOrder}
|
||||||
|
>
|
||||||
|
取消订单
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size='small'
|
||||||
|
className='flex-1'
|
||||||
|
style={{ backgroundColor: '#4b9cf5' }}
|
||||||
|
onClick={() => Taro.makePhoneCall({ phoneNumber: '400-000-0000' })}
|
||||||
|
>
|
||||||
|
联系客服
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 待发货: 退款 + 联系客服 */}
|
{/* 待发货: 退款 + 联系客服 */}
|
||||||
{phase === 'unshipped' && (
|
{phase === 'unshipped' && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -125,7 +125,13 @@ const CheckoutPage: React.FC = () => {
|
|||||||
const [rushBuyProducts, setRushBuyProducts] = useState<ShopGoods[]>([])
|
const [rushBuyProducts, setRushBuyProducts] = useState<ShopGoods[]>([])
|
||||||
|
|
||||||
// 支付方式相关状态
|
// 支付方式相关状态
|
||||||
const [payType, setPayType] = useState<number>(8) // 8: 货到付款
|
const [payType, setPayType] = useState<number>(8) // 8: 货到付款(默认),9: 线下付款
|
||||||
|
|
||||||
|
// 支付方式选项
|
||||||
|
const PAYMENT_OPTIONS = [
|
||||||
|
{ id: 8, name: '货到付款', desc: '送达时支付', icon: '货', bgColor: '#f0fdf4', iconBg: '#dbeafe', iconColor: '#2563eb', borderColor: '#22c55e' },
|
||||||
|
{ id: 9, name: '线下付款', desc: '微信转账,商家确认后发货', icon: '线', bgColor: '#fffbeb', iconBg: '#fef3c7', iconColor: '#d97706', borderColor: '#f59e0b' },
|
||||||
|
]
|
||||||
|
|
||||||
// 从本地存储获取立即购买的数据
|
// 从本地存储获取立即购买的数据
|
||||||
const buyNowItems = useMemo(() => {
|
const buyNowItems = useMemo(() => {
|
||||||
@@ -263,7 +269,8 @@ const CheckoutPage: React.FC = () => {
|
|||||||
specInfo: item.skuSpec || item.sku?.sku || item.product?.specName,
|
specInfo: item.skuSpec || item.sku?.sku || item.product?.specName,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 货到付款:后端会自动设置 payStatus=true,无需前端传递 payStatus/payTime
|
// 货到付款(8):后端会自动设置 payStatus=true,无需前端传递 payStatus/payTime
|
||||||
|
// 线下付款(9):后端会设置 payStatus=false,等待商家确认收款后才设为 true
|
||||||
const orderParams: OrderCreateRequest = {
|
const orderParams: OrderCreateRequest = {
|
||||||
goodsItems,
|
goodsItems,
|
||||||
addressId: defaultAddress.id,
|
addressId: defaultAddress.id,
|
||||||
@@ -441,25 +448,45 @@ const CheckoutPage: React.FC = () => {
|
|||||||
<View className='bg-white rounded-lg mt-3 p-3'>
|
<View className='bg-white rounded-lg mt-3 p-3'>
|
||||||
<Text className='text-sm font-medium text-gray-800 mb-3 block'>支付方式</Text>
|
<Text className='text-sm font-medium text-gray-800 mb-3 block'>支付方式</Text>
|
||||||
|
|
||||||
{/* 货到付款 */}
|
{PAYMENT_OPTIONS.map((option) => {
|
||||||
|
const selected = payType === option.id
|
||||||
|
return (
|
||||||
<View
|
<View
|
||||||
className='flex items-center py-3 px-2 rounded-lg'
|
key={option.id}
|
||||||
style={{ backgroundColor: '#f0fdf4' }}
|
className='flex items-center py-3 px-2 rounded-lg mb-2'
|
||||||
|
style={{ backgroundColor: selected ? option.bgColor : '#f9fafb' }}
|
||||||
|
onClick={() => setPayType(option.id)}
|
||||||
>
|
>
|
||||||
<View className='w-8 h-8 rounded-full bg-blue-50 flex items-center justify-center mr-3'>
|
<View
|
||||||
<Text className='text-sm'>货</Text>
|
className='w-8 h-8 rounded-full flex items-center justify-center mr-3'
|
||||||
|
style={{ backgroundColor: option.iconBg }}
|
||||||
|
>
|
||||||
|
<Text className='text-sm' style={{ color: option.iconColor }}>{option.icon}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className='flex-1'>
|
<View className='flex-1'>
|
||||||
<Text className='text-sm text-gray-800 block'>货到付款</Text>
|
<Text className='text-sm text-gray-800 block'>{option.name}</Text>
|
||||||
<Text className='text-xs text-gray-400 block'>送达时支付</Text>
|
<Text className='text-xs text-gray-400 block'>{option.desc}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
className='w-5 h-5 rounded-full border-2 flex items-center justify-center'
|
className='w-5 h-5 rounded-full border-2 flex items-center justify-center'
|
||||||
style={{ borderColor: '#22c55e' }}
|
style={{ borderColor: selected ? option.borderColor : '#d1d5db' }}
|
||||||
>
|
>
|
||||||
<View className='w-2 h-2 rounded-full' style={{ backgroundColor: '#22c55e' }} />
|
{selected && (
|
||||||
|
<View className='w-2 h-2 rounded-full' style={{ backgroundColor: option.borderColor }} />
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* 线下付款选中时显示提示 */}
|
||||||
|
{payType === 9 && (
|
||||||
|
<View className='mt-2 p-2 rounded-lg' style={{ backgroundColor: '#fef3c7' }}>
|
||||||
|
<Text className='text-xs text-orange-600'>
|
||||||
|
请下单后通过微信转账付款,商家确认收款后发货。
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 凑单推荐 */}
|
{/* 凑单推荐 */}
|
||||||
|
|||||||
@@ -92,7 +92,11 @@ function parseSendEndImg(raw: string): string[] {
|
|||||||
function getStatusText(order: ShopOrder): string {
|
function getStatusText(order: ShopOrder): string {
|
||||||
if (order.orderStatus === 1) return '已完成'
|
if (order.orderStatus === 1) return '已完成'
|
||||||
if (order.orderStatus === 2) return '已关闭'
|
if (order.orderStatus === 2) return '已关闭'
|
||||||
if (order.payStatus === false || order.payStatus === null) return '待收款'
|
if (order.payStatus === false || order.payStatus === null) {
|
||||||
|
// 线下付款待确认收款
|
||||||
|
if (order.payType === 9) return '待确认收款'
|
||||||
|
return '待收款'
|
||||||
|
}
|
||||||
if (order.payStatus && order.deliveryStatus === 10) return '待发货'
|
if (order.payStatus && order.deliveryStatus === 10) return '待发货'
|
||||||
if (order.payStatus && order.deliveryStatus === 20) return '待收货'
|
if (order.payStatus && order.deliveryStatus === 20) return '待收货'
|
||||||
return '进行中'
|
return '进行中'
|
||||||
@@ -413,7 +417,7 @@ export default function StoreOrdersPage() {
|
|||||||
{/* 金额 + 支付方式 */}
|
{/* 金额 + 支付方式 */}
|
||||||
<View className='flex justify-between items-center mb-3'>
|
<View className='flex justify-between items-center mb-3'>
|
||||||
<Text className='text-xs text-gray-400'>
|
<Text className='text-xs text-gray-400'>
|
||||||
{order.payType === 0 ? '货到付款' : order.payType === 4 ? '现金支付' : '货到付款'}
|
{order.payType === 8 ? '货到付款' : order.payType === 9 ? '线下付款' : order.payType === 4 ? '现金支付' : order.payType === 1 ? '微信支付' : '其他'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className='text-sm text-gray-700'>
|
<Text className='text-sm text-gray-700'>
|
||||||
实付:<Text className='text-red-500 font-medium'>¥{order.payPrice || order.totalPrice}</Text>
|
实付:<Text className='text-red-500 font-medium'>¥{order.payPrice || order.totalPrice}</Text>
|
||||||
|
|||||||
@@ -465,7 +465,7 @@ const AddressEditPage: React.FC = () => {
|
|||||||
{/* 地址智能识别区 */}
|
{/* 地址智能识别区 */}
|
||||||
<View className="bg-white px-3 pt-3 pb-2">
|
<View className="bg-white px-3 pt-3 pb-2">
|
||||||
<View
|
<View
|
||||||
className="rounded-lg p-2 relative"
|
className="rounded-lg p-2"
|
||||||
style={{border: '1px dashed #0e932e'}}
|
style={{border: '1px dashed #0e932e'}}
|
||||||
>
|
>
|
||||||
<Textarea
|
<Textarea
|
||||||
@@ -475,8 +475,8 @@ const AddressEditPage: React.FC = () => {
|
|||||||
placeholder="粘贴文本,点击「识别」自动识别收货人、地址、电话"
|
placeholder="粘贴文本,点击「识别」自动识别收货人、地址、电话"
|
||||||
maxlength={200}
|
maxlength={200}
|
||||||
/>
|
/>
|
||||||
|
<View className="flex justify-end mt-2">
|
||||||
<View
|
<View
|
||||||
className="absolute right-2 bottom-2"
|
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: '#0e932e',
|
backgroundColor: '#0e932e',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
@@ -491,6 +491,7 @@ const AddressEditPage: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View className="bg-gray-100" style={{height: '8px'}}/>
|
<View className="bg-gray-100" style={{height: '8px'}}/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user