feat(shop): 优化VIP价格支持及线下付款兼容

- 结算页新增线下付款异常友好提示,避免支付配置中appid缺失错误
- 后端修复支付类型判断,防止线下付款仍试图创建微信支付订单
- 结算页默认支付方式调整为线下付款,货到付款选项注释
- 新增门店订单“修改金额”功能,支持弹窗输入新金额和修改原因
- 订单修改支持追加操作记录,接口复用updateShopOrder,无需新接口
- 重构多处页面和组件,使用响应式useVipStatus替代同步isVipMember缓存
- 新增OrderGoodsItem.price字段,下单时传VIP dealerPrice确保价格准确
- 购物车、商品详情、分类页、SkuSelector、商品卡片等支持动态VIP价格展示
- 修改购物车上下文calcPrice函数,响应VIP状态变更自动更新价格显示
- 提升前端VIP状态管理一致性,防止过期缓存导致VIP特权价格显示异常
- 验证构建通过,后端服务重启后生效线下付款修复和订单修改功能
This commit is contained in:
2026-07-15 02:45:19 +08:00
parent 747a2a1d3a
commit e47a0d3d3e
14 changed files with 556 additions and 41 deletions

View File

@@ -1,5 +1,5 @@
import React, {useState, useEffect, useCallback, useRef} from 'react'
import {View, Text, Image, ScrollView} from '@tarojs/components'
import {View, Text, Image, ScrollView, Input, Textarea} from '@tarojs/components'
import Taro, {useDidShow} from '@tarojs/taro'
import {pageShopOrder, updateShopOrder, removeShopOrder} from '@/api/shop/shopOrder'
import type {ShopOrder, ShopOrderParam} from '@/api/shop/shopOrder/model'
@@ -24,16 +24,18 @@ const TABS: { key: TabKey; label: string; params: Partial<ShopOrderParam>[] }[]
]
// ─── 操作类型 ─────────────────────────────────────────────────────
type OpType = 'pay' | 'complete'
type OpType = 'pay' | 'complete' | 'editPrice'
const OP_LABEL: Record<OpType, string> = {
pay: '已收款',
complete: '已完成',
editPrice: '金额',
}
const OP_DESC: Record<OpType, string> = {
pay: '变更支付状态为已付款',
complete: '同时变更支付状态为已付款、收货状态为已收货、订单状态为已完成',
editPrice: '修改订单实付金额',
}
// ─── 图片上传 ─────────────────────────────────────────────────────
@@ -119,6 +121,8 @@ function isOrderActionable(order: ShopOrder): boolean {
function getOrderActions(order: ShopOrder): { label: string; type: OpType }[] {
if (!isOrderActionable(order)) return []
const actions: { label: string; type: OpType }[] = []
// 修改金额(门店权限)
actions.push({label: '修改金额', type: 'editPrice'})
// 货到付款:未付款 / 已付款 都直接"确认完成"
// 确认完成会同时设置 payStatus=true无需单独的"确认收款"按钮
if (order.orderStatus !== 1 && order.orderStatus !== 2) {
@@ -144,6 +148,11 @@ export default function StoreOrdersPage() {
const [proofImages, setProofImages] = useState<string[]>([])
const [submitting, setSubmitting] = useState(false)
// 修改金额弹窗状态
const [showEditPriceModal, setShowEditPriceModal] = useState(false)
const [editPayPrice, setEditPayPrice] = useState('')
const [editReason, setEditReason] = useState('')
const pageSize = 10
const loadingRef = useRef(false)
@@ -246,6 +255,57 @@ export default function StoreOrdersPage() {
setProofImages([])
}
/** 打开修改金额弹窗 */
const openEditPriceModal = (order: ShopOrder) => {
setCurrentOrder(order)
setEditPayPrice(String(order.payPrice || order.totalPrice || ''))
setEditReason('')
setShowEditPriceModal(true)
}
/** 关闭修改金额弹窗 */
const closeEditPriceModal = () => {
setShowEditPriceModal(false)
setCurrentOrder(null)
setEditPayPrice('')
setEditReason('')
}
/** 提交修改金额 */
const submitEditPrice = async () => {
if (!currentOrder) return
const newPrice = parseFloat(editPayPrice)
if (isNaN(newPrice) || newPrice < 0) {
Taro.showToast({title: '请输入有效的金额', icon: 'none'})
return
}
if (!editReason.trim()) {
Taro.showToast({title: '请输入修改原因', icon: 'none'})
return
}
setSubmitting(true)
try {
const oldPrice = currentOrder.payPrice || currentOrder.totalPrice || '0'
const changeRecord = `【门店修改金额】原实付¥${oldPrice} → 新实付¥${newPrice.toFixed(2)},原因:${editReason.trim()}`
await updateShopOrder({
orderId: currentOrder.orderId,
payPrice: newPrice.toFixed(2),
comments: (currentOrder.comments || '') + `\n${changeRecord}`,
} as ShopOrder)
Taro.showToast({title: '修改成功', icon: 'success'})
closeEditPriceModal()
loadOrders(activeTab, 1)
} catch (e: any) {
Taro.showToast({title: e.message || '修改失败', icon: 'none'})
} finally {
setSubmitting(false)
}
}
/** 选择并上传凭证图片 */
const chooseProofImage = () => {
const maxCount = 3
@@ -445,12 +505,14 @@ export default function StoreOrdersPage() {
className={`px-4 py-2 rounded-lg ${
act.type === 'complete'
? 'bg-green-500'
: act.type === 'editPrice'
? 'bg-orange-500'
: 'border border-blue-500'
}`}
onClick={() => openModal(order, act.type)}
onClick={() => act.type === 'editPrice' ? openEditPriceModal(order) : openModal(order, act.type)}
>
<Text className={`text-sm ${
act.type === 'complete' ? 'text-white' : 'text-blue-500'
act.type === 'complete' || act.type === 'editPrice' ? 'text-white' : 'text-blue-500'
}`}>{act.label}</Text>
</View>
))}
@@ -594,6 +656,70 @@ export default function StoreOrdersPage() {
</View>
</View>
)}
{/* 修改金额弹窗 */}
{showEditPriceModal && currentOrder && (
<View className='fixed inset-0 z-50 flex items-end justify-center'>
{/* 遮罩 */}
<View className='absolute inset-0 bg-black/50' onClick={closeEditPriceModal}/>
{/* 弹窗内容 */}
<View className='relative bg-white rounded-t-2xl w-full px-5 pt-6 pb-10'>
<Text className='text-lg font-medium text-gray-800 text-center mb-5 block'>
</Text>
{/* 订单信息 */}
<View className='bg-gray-50 rounded-xl p-4 mb-5'>
<Text className='text-sm text-gray-700 block'>{currentOrder.orderNo}</Text>
<Text className='text-sm text-gray-700 mt-1 block'>
<Text className='text-red-500 font-medium'>¥{currentOrder.payPrice || currentOrder.totalPrice}</Text>
</Text>
</View>
{/* 新金额输入 */}
<View className='mb-5'>
<Text className='text-sm text-gray-600 mb-2 block'></Text>
<Input
type='digit'
className='w-full px-4 py-3 rounded-xl border border-gray-200 text-lg text-gray-800'
value={editPayPrice}
onInput={(e) => setEditPayPrice(e.detail.value)}
placeholder='请输入新的实付金额'
/>
</View>
{/* 修改原因 */}
<View className='mb-6'>
<Text className='text-sm text-gray-600 mb-2 block'></Text>
<Textarea
className='w-full px-4 py-3 rounded-xl border border-gray-200 text-sm text-gray-800'
style={{minHeight: '60px'}}
value={editReason}
onInput={(e) => setEditReason(e.detail.value)}
placeholder='请输入修改原因,如:商品缺货调整、协商降价等'
maxlength={100}
/>
</View>
{/* 操作按钮 */}
<View className='flex gap-3'>
<View className='flex-1 py-3 rounded-xl bg-gray-100 text-center' onClick={closeEditPriceModal}>
<Text className='text-sm text-gray-600'></Text>
</View>
<View
className='flex-1 py-3 rounded-xl bg-orange-500 text-center flex items-center justify-center'
onClick={submitting ? undefined : submitEditPrice}
>
{submitting ? (
<Text className='text-sm text-white'>...</Text>
) : (
<Text className='text-sm text-white'></Text>
)}
</View>
</View>
</View>
</View>
)}
</View>
)
}