feat(payment): 新增线下付款方式及相关订单状态支持

- 支付页面新增线下付款选项,支持微信转账付款方式
- 订单流程新增线下付款待确认(offline_pending)阶段
- 订单详情与订单卡片显示线下付款状态及提示信息
- 支付接口和模型增加线下付款支付方式(payType=9)支持
- 订单列表页区分线下付款待确认与待收款状态
- 地址编辑页修复智能识别按钮无法点击问题,调整布局避免被Textarea遮挡
This commit is contained in:
2026-07-14 22:15:31 +08:00
parent 44b3cb10be
commit dfbcbcf615
7 changed files with 116 additions and 40 deletions

View File

@@ -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 上。

View File

@@ -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;

View File

@@ -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' }

View File

@@ -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' && (
<> <>

View File

@@ -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) => {
<View const selected = payType === option.id
className='flex items-center py-3 px-2 rounded-lg' return (
style={{ backgroundColor: '#f0fdf4' }} <View
> key={option.id}
<View className='w-8 h-8 rounded-full bg-blue-50 flex items-center justify-center mr-3'> className='flex items-center py-3 px-2 rounded-lg mb-2'
<Text className='text-sm'></Text> style={{ backgroundColor: selected ? option.bgColor : '#f9fafb' }}
onClick={() => setPayType(option.id)}
>
<View
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 className='flex-1'>
<Text className='text-sm text-gray-800 block'>{option.name}</Text>
<Text className='text-xs text-gray-400 block'>{option.desc}</Text>
</View>
<View
className='w-5 h-5 rounded-full border-2 flex items-center justify-center'
style={{ borderColor: selected ? option.borderColor : '#d1d5db' }}
>
{selected && (
<View className='w-2 h-2 rounded-full' style={{ backgroundColor: option.borderColor }} />
)}
</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 className='flex-1'> )}
<Text className='text-sm text-gray-800 block'></Text>
<Text className='text-xs text-gray-400 block'></Text>
</View>
<View
className='w-5 h-5 rounded-full border-2 flex items-center justify-center'
style={{ borderColor: '#22c55e' }}
>
<View className='w-2 h-2 rounded-full' style={{ backgroundColor: '#22c55e' }} />
</View>
</View>
</View> </View>
{/* 凑单推荐 */} {/* 凑单推荐 */}

View File

@@ -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>

View File

@@ -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,19 +475,20 @@ const AddressEditPage: React.FC = () => {
placeholder="粘贴文本,点击「识别」自动识别收货人、地址、电话" placeholder="粘贴文本,点击「识别」自动识别收货人、地址、电话"
maxlength={200} maxlength={200}
/> />
<View <View className="flex justify-end mt-2">
className="absolute right-2 bottom-2" <View
style={{ style={{
backgroundColor: '#0e932e', backgroundColor: '#0e932e',
borderRadius: '12px', borderRadius: '12px',
paddingLeft: '12px', paddingLeft: '12px',
paddingRight: '12px', paddingRight: '12px',
paddingTop: '4px', paddingTop: '4px',
paddingBottom: '4px' paddingBottom: '4px'
}} }}
onClick={recognizeAddress} onClick={recognizeAddress}
> >
<Text className="text-white text-xs"></Text> <Text className="text-white text-xs"></Text>
</View>
</View> </View>
</View> </View>
</View> </View>