refactor(ticket): 优化水票功能实现逻辑
- 移除手动选择水票功能,改为自动按数量少优先消耗 - 新增 ticketLoaded 状态跟踪水票加载完成情况 - 实现 getTicketAvailableQty 函数统一处理不同租户的可用数量字段差异 - 修改水票过滤逻辑,支持多种状态字段格式并改进商品ID匹配 - 更新下单流程,将单个订单拆分为多个水票订单以支持批量消耗 - 优化水票弹窗界面显示可用总数和消耗顺序说明 - 移除选中水票的相关状态管理和UI组件 - 更新下单确认提示显示优先使用数量少的水票策略
This commit is contained in:
@@ -68,9 +68,9 @@ const OrderConfirm = () => {
|
|||||||
|
|
||||||
// 水票:用于“立即送水”下单(用水票抵扣,无需支付)
|
// 水票:用于“立即送水”下单(用水票抵扣,无需支付)
|
||||||
const [tickets, setTickets] = useState<GltUserTicket[]>([])
|
const [tickets, setTickets] = useState<GltUserTicket[]>([])
|
||||||
const [selectedTicketId, setSelectedTicketId] = useState<number | undefined>(undefined)
|
|
||||||
const [ticketPopupVisible, setTicketPopupVisible] = useState(false)
|
const [ticketPopupVisible, setTicketPopupVisible] = useState(false)
|
||||||
const [ticketLoading, setTicketLoading] = useState(false)
|
const [ticketLoading, setTicketLoading] = useState(false)
|
||||||
|
const [ticketLoaded, setTicketLoaded] = useState(false)
|
||||||
const noTicketPromptedRef = useRef(false)
|
const noTicketPromptedRef = useRef(false)
|
||||||
|
|
||||||
// Delivery range (geofence): block ordering if address/current location is outside.
|
// Delivery range (geofence): block ordering if address/current location is outside.
|
||||||
@@ -95,15 +95,42 @@ const OrderConfirm = () => {
|
|||||||
return Number.isFinite(id) && id > 0 ? id : undefined
|
return Number.isFinite(id) && id > 0 ? id : undefined
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const getTicketAvailableQty = (t?: Partial<GltUserTicket> | null) => {
|
||||||
|
if (!t) return 0
|
||||||
|
const anyT: any = t
|
||||||
|
const raw =
|
||||||
|
anyT.availableQty ??
|
||||||
|
anyT.availableNum ??
|
||||||
|
anyT.availableCount ??
|
||||||
|
anyT.remainQty ??
|
||||||
|
anyT.remainNum ??
|
||||||
|
anyT.remainCount
|
||||||
|
const n = Number(raw)
|
||||||
|
if (Number.isFinite(n)) return n
|
||||||
|
|
||||||
|
// Fallback for tenants that don't return `availableQty`.
|
||||||
|
const total = Number(anyT.totalQty ?? anyT.totalNum ?? anyT.totalCount ?? 0)
|
||||||
|
const used = Number(anyT.usedQty ?? anyT.usedNum ?? anyT.usedCount ?? 0)
|
||||||
|
const frozen = Number(anyT.frozenQty ?? anyT.frozenNum ?? anyT.frozenCount ?? 0)
|
||||||
|
const computed = (Number.isFinite(total) ? total : 0) - (Number.isFinite(used) ? used : 0) - (Number.isFinite(frozen) ? frozen : 0)
|
||||||
|
return Number.isFinite(computed) ? computed : 0
|
||||||
|
}
|
||||||
|
|
||||||
const usableTickets = useMemo(() => {
|
const usableTickets = useMemo(() => {
|
||||||
const list = (tickets || [])
|
const list = (tickets || [])
|
||||||
.filter(t => t?.deleted !== 1)
|
.filter(t => Number(t?.deleted) !== 1)
|
||||||
.filter(t => t?.status !== 1)
|
// 1 = 冻结(兼容 status 为字符串)
|
||||||
.filter(t => Number.isFinite(Number(t?.id)) && Number(t.id) > 0)
|
.filter(t => Number(t?.status) !== 1)
|
||||||
.filter(t => (t.availableQty ?? 0) > 0)
|
.filter(t => Number.isFinite(Number(t?.id)) && Number(t?.id) > 0)
|
||||||
// Some tenants don't fill goodsId on ticket; allow it as a fallback.
|
.filter(t => getTicketAvailableQty(t) > 0)
|
||||||
.filter(t => (numericGoodsId ? (!t.goodsId || t.goodsId === numericGoodsId) : true))
|
// Some tenants return goodsId as string; coerce before comparison.
|
||||||
// FIFO: use older tickets first (reduce disputes).
|
.filter((t) => {
|
||||||
|
if (!numericGoodsId) return true
|
||||||
|
const tg = Number((t as any)?.goodsId)
|
||||||
|
const hasGoodsId = Number.isFinite(tg) && tg > 0
|
||||||
|
return !hasGoodsId || tg === numericGoodsId
|
||||||
|
})
|
||||||
|
// Default order in list: older first (reduce disputes). Real consumption order is computed separately.
|
||||||
return list.sort((a, b) => {
|
return list.sort((a, b) => {
|
||||||
const ta = new Date(a.createTime || 0).getTime() || 0
|
const ta = new Date(a.createTime || 0).getTime() || 0
|
||||||
const tb = new Date(b.createTime || 0).getTime() || 0
|
const tb = new Date(b.createTime || 0).getTime() || 0
|
||||||
@@ -112,19 +139,28 @@ const OrderConfirm = () => {
|
|||||||
})
|
})
|
||||||
}, [tickets, numericGoodsId])
|
}, [tickets, numericGoodsId])
|
||||||
|
|
||||||
const selectedTicket = useMemo(() => {
|
|
||||||
if (!selectedTicketId) return undefined
|
|
||||||
return usableTickets.find(t => Number(t.id) === Number(selectedTicketId))
|
|
||||||
}, [usableTickets, selectedTicketId])
|
|
||||||
|
|
||||||
const availableTicketTotal = useMemo(() => {
|
const availableTicketTotal = useMemo(() => {
|
||||||
return Number(selectedTicket?.availableQty || 0)
|
return usableTickets.reduce((sum, t) => sum + getTicketAvailableQty(t), 0)
|
||||||
}, [selectedTicket?.availableQty])
|
}, [usableTickets])
|
||||||
|
|
||||||
|
// Consume tickets with smaller available qty first; ties: older first.
|
||||||
|
const ticketsToConsume = useMemo(() => {
|
||||||
|
const list = [...usableTickets]
|
||||||
|
return list.sort((a, b) => {
|
||||||
|
const qa = getTicketAvailableQty(a)
|
||||||
|
const qb = getTicketAvailableQty(b)
|
||||||
|
if (qa !== qb) return qa - qb
|
||||||
|
const ta = new Date(a.createTime || 0).getTime() || 0
|
||||||
|
const tb = new Date(b.createTime || 0).getTime() || 0
|
||||||
|
if (ta !== tb) return ta - tb
|
||||||
|
return (a.id || 0) - (b.id || 0)
|
||||||
|
})
|
||||||
|
}, [usableTickets])
|
||||||
|
|
||||||
const noUsableTickets = useMemo(() => {
|
const noUsableTickets = useMemo(() => {
|
||||||
// Only show "go buy tickets" guidance after we have finished loading.
|
// Only show "go buy tickets" guidance after we have finished loading.
|
||||||
return !!userId && !ticketLoading && usableTickets.length === 0
|
return !!userId && ticketLoaded && !ticketLoading && usableTickets.length === 0
|
||||||
}, [ticketLoading, usableTickets.length, userId])
|
}, [ticketLoaded, ticketLoading, usableTickets.length, userId])
|
||||||
|
|
||||||
const maxQuantity = useMemo(() => {
|
const maxQuantity = useMemo(() => {
|
||||||
const stockMax = goods?.stock ?? 999
|
const stockMax = goods?.stock ?? 999
|
||||||
@@ -420,11 +456,14 @@ const OrderConfirm = () => {
|
|||||||
if (ticketLoading) return
|
if (ticketLoading) return
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
setTickets([])
|
setTickets([])
|
||||||
|
setTicketLoaded(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setTicketLoading(true)
|
setTicketLoading(true)
|
||||||
const list = await listGltUserTicket({ userId, status: 0 })
|
// Do not pass `status` here: some backends use different status semantics;
|
||||||
|
// we filter out frozen tickets on the client for compatibility.
|
||||||
|
const list = await listGltUserTicket({ userId })
|
||||||
setTickets(list || [])
|
setTickets(list || [])
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('获取水票失败:', e)
|
console.error('获取水票失败:', e)
|
||||||
@@ -432,6 +471,7 @@ const OrderConfirm = () => {
|
|||||||
Taro.showToast({ title: '获取水票失败', icon: 'none' })
|
Taro.showToast({ title: '获取水票失败', icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
setTicketLoading(false)
|
setTicketLoading(false)
|
||||||
|
setTicketLoaded(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,15 +505,20 @@ const OrderConfirm = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure ticket list is loaded.
|
||||||
|
if (ticketLoading) {
|
||||||
|
Taro.showToast({ title: '水票加载中,请稍后再试', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!ticketLoaded) {
|
||||||
|
await loadUserTickets()
|
||||||
|
}
|
||||||
|
|
||||||
const storeForOrder = await resolveStoreForOrder()
|
const storeForOrder = await resolveStoreForOrder()
|
||||||
if (!storeForOrder?.id) {
|
if (!storeForOrder?.id) {
|
||||||
Taro.showToast({ title: '未找到可配送门店,请先在首页选择门店或联系管理员配置门店坐标', icon: 'none' })
|
Taro.showToast({ title: '未找到可配送门店,请先在首页选择门店或联系管理员配置门店坐标', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!selectedTicket?.id) {
|
|
||||||
Taro.showToast({ title: '请选择水票', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (availableTicketTotal <= 0) {
|
if (availableTicketTotal <= 0) {
|
||||||
Taro.showToast({ title: '暂无可用水票', icon: 'none' })
|
Taro.showToast({ title: '暂无可用水票', icon: 'none' })
|
||||||
return
|
return
|
||||||
@@ -507,7 +552,7 @@ const OrderConfirm = () => {
|
|||||||
|
|
||||||
const confirmRes = await Taro.showModal({
|
const confirmRes = await Taro.showModal({
|
||||||
title: '确认下单',
|
title: '确认下单',
|
||||||
content: `配送时间:${sendTimeText}\n将使用 ${finalQty} 张水票下单,送水 ${finalQty} 桶,是否确认?`
|
content: `配送时间:${sendTimeText}\n将使用 ${finalQty} 张水票下单(优先使用可用数量少的水票),送水 ${finalQty} 桶,是否确认?`
|
||||||
})
|
})
|
||||||
if (!confirmRes.confirm) return
|
if (!confirmRes.confirm) return
|
||||||
|
|
||||||
@@ -518,24 +563,41 @@ const OrderConfirm = () => {
|
|||||||
// Best-effort auto dispatch rider. If it fails, backend/manual dispatch can still handle it.
|
// Best-effort auto dispatch rider. If it fails, backend/manual dispatch can still handle it.
|
||||||
const autoRider = storeForOrder.id ? await resolveAutoRiderForStore(storeForOrder.id) : null
|
const autoRider = storeForOrder.id ? await resolveAutoRiderForStore(storeForOrder.id) : null
|
||||||
|
|
||||||
await addGltTicketOrder({
|
// Split this "delivery" into multiple ticket orders (backend order model binds to one userTicketId).
|
||||||
userTicketId: selectedTicket.id,
|
// Consume tickets with smaller available qty first.
|
||||||
storeId: storeForOrder.id,
|
let remain = finalQty
|
||||||
addressId: address.id,
|
let created = 0
|
||||||
totalNum: finalQty,
|
for (const t of ticketsToConsume) {
|
||||||
buyerRemarks: orderRemark,
|
if (remain <= 0) break
|
||||||
sendTime: dayjs(sendTime).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
const avail = getTicketAvailableQty(t)
|
||||||
// Backend may take userId from token; pass-through is harmless if backend ignores it.
|
const useQty = Math.min(remain, avail)
|
||||||
userId,
|
if (useQty <= 0) continue
|
||||||
riderId: Number.isFinite(Number(autoRider?.userId)) ? Number(autoRider?.userId) : undefined,
|
await addGltTicketOrder({
|
||||||
riderName: autoRider?.realName,
|
userTicketId: Number(t.id),
|
||||||
riderPhone: autoRider?.mobile,
|
storeId: storeForOrder.id,
|
||||||
comments: goods.name ? `立即送水:${goods.name}` : '立即送水'
|
addressId: address.id,
|
||||||
})
|
totalNum: useQty,
|
||||||
|
buyerRemarks: orderRemark,
|
||||||
|
sendTime: dayjs(sendTime).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
// Backend may take userId from token; pass-through is harmless if backend ignores it.
|
||||||
|
userId,
|
||||||
|
riderId: Number.isFinite(Number(autoRider?.userId)) ? Number(autoRider?.userId) : undefined,
|
||||||
|
riderName: autoRider?.realName,
|
||||||
|
riderPhone: autoRider?.mobile,
|
||||||
|
comments: goods.name ? `立即送水:${goods.name}` : '立即送水'
|
||||||
|
})
|
||||||
|
remain -= useQty
|
||||||
|
created += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remain > 0) {
|
||||||
|
// Ticket counts might have changed between loading and submission.
|
||||||
|
throw new Error('水票可用次数不足,请刷新后重试')
|
||||||
|
}
|
||||||
|
|
||||||
await loadUserTickets()
|
await loadUserTickets()
|
||||||
|
|
||||||
Taro.showToast({ title: '下单成功', icon: 'success' })
|
Taro.showToast({ title: created > 1 ? '下单成功(已拆分多张水票)' : '下单成功', icon: 'success' })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// 跳转到“我的送水订单”
|
// 跳转到“我的送水订单”
|
||||||
Taro.redirectTo({ url: '/user/ticket/index?tab=order' })
|
Taro.redirectTo({ url: '/user/ticket/index?tab=order' })
|
||||||
@@ -633,18 +695,6 @@ const OrderConfirm = () => {
|
|||||||
})
|
})
|
||||||
}, [maxQuantity])
|
}, [maxQuantity])
|
||||||
|
|
||||||
// Auto-pick a default ticket (first usable) when ticket list changes.
|
|
||||||
useEffect(() => {
|
|
||||||
if (!usableTickets.length) {
|
|
||||||
setSelectedTicketId(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const currentValid = selectedTicketId && usableTickets.some(t => Number(t.id) === Number(selectedTicketId))
|
|
||||||
if (!currentValid) {
|
|
||||||
setSelectedTicketId(Number(usableTickets[0].id))
|
|
||||||
}
|
|
||||||
}, [usableTickets, selectedTicketId])
|
|
||||||
|
|
||||||
// If user has no usable tickets, proactively guide them to purchase (only once per page lifecycle).
|
// If user has no usable tickets, proactively guide them to purchase (only once per page lifecycle).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!noUsableTickets) return
|
if (!noUsableTickets) return
|
||||||
@@ -772,42 +822,50 @@ const OrderConfirm = () => {
|
|||||||
/>
|
/>
|
||||||
</CellGroup>
|
</CellGroup>
|
||||||
|
|
||||||
<CellGroup>
|
<CellGroup>
|
||||||
<Cell
|
<Cell
|
||||||
title={(
|
title={(
|
||||||
<View className="flex items-center gap-2">
|
<View className="flex items-center gap-2">
|
||||||
<Ticket className={'text-gray-500'}/>
|
<Ticket className={'text-gray-500'}/>
|
||||||
<Text>选择水票</Text>
|
<Text>水票明细</Text>
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
extra={(
|
|
||||||
<View className={'flex items-center gap-2'}>
|
|
||||||
<View className={'text-gray-900'}>
|
|
||||||
{ticketLoading
|
|
||||||
? '加载中...'
|
|
||||||
: (selectedTicket
|
|
||||||
? `${selectedTicket.templateName || '水票'}(可用${selectedTicket.availableQty ?? 0})`
|
|
||||||
: (noUsableTickets ? '暂无可用水票' : '请选择')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</View>
|
|
||||||
<ArrowRight className={'text-gray-400'} size={14}/>
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
onClick={async () => {
|
extra={(
|
||||||
if (ticketLoading) return
|
<View className={'flex items-center gap-2'}>
|
||||||
if (noUsableTickets) {
|
<View className={'text-gray-900'}>
|
||||||
const r = await Taro.showModal({
|
{ticketLoading
|
||||||
title: '暂无可用水票',
|
? '加载中...'
|
||||||
content: '您还没有可用水票,是否前往购买?',
|
: (ticketLoaded
|
||||||
confirmText: '去购买',
|
? (noUsableTickets
|
||||||
cancelText: '暂不'
|
? '暂无可用水票'
|
||||||
})
|
: `可用合计 ${availableTicketTotal}(${usableTickets.length}组)`
|
||||||
if (r.confirm) await goBuyTickets()
|
)
|
||||||
return
|
: '点击查看'
|
||||||
}
|
)
|
||||||
setTicketPopupVisible(true)
|
}
|
||||||
}}
|
</View>
|
||||||
|
<ArrowRight className={'text-gray-400'} size={14}/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
onClick={async () => {
|
||||||
|
if (ticketLoading) return
|
||||||
|
if (!ticketLoaded) {
|
||||||
|
setTicketPopupVisible(true)
|
||||||
|
await loadUserTickets()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (noUsableTickets) {
|
||||||
|
const r = await Taro.showModal({
|
||||||
|
title: '暂无可用水票',
|
||||||
|
content: '您还没有可用水票,是否前往购买?',
|
||||||
|
confirmText: '去购买',
|
||||||
|
cancelText: '暂不'
|
||||||
|
})
|
||||||
|
if (r.confirm) await goBuyTickets()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setTicketPopupVisible(true)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{noUsableTickets && (
|
{noUsableTickets && (
|
||||||
<Cell
|
<Cell
|
||||||
@@ -838,50 +896,50 @@ const OrderConfirm = () => {
|
|||||||
)}/>
|
)}/>
|
||||||
</CellGroup>
|
</CellGroup>
|
||||||
|
|
||||||
{/* 水票明细弹窗 */}
|
{/* 水票明细弹窗 */}
|
||||||
<Popup
|
<Popup
|
||||||
visible={ticketPopupVisible}
|
visible={ticketPopupVisible}
|
||||||
position="bottom"
|
position="bottom"
|
||||||
style={{ height: '70vh' }}
|
style={{ height: '70vh' }}
|
||||||
onClose={() => setTicketPopupVisible(false)}
|
onClose={() => setTicketPopupVisible(false)}
|
||||||
>
|
>
|
||||||
<View className="p-4">
|
<View className="p-4">
|
||||||
<View className="flex justify-between items-center mb-3">
|
<View className="flex justify-between items-center mb-3">
|
||||||
<Text className="text-base font-medium">水票明细</Text>
|
<Text className="text-base font-medium">水票明细</Text>
|
||||||
<Text
|
<Text
|
||||||
className="text-sm text-gray-500"
|
className="text-sm text-gray-500"
|
||||||
onClick={() => setTicketPopupVisible(false)}
|
onClick={() => setTicketPopupVisible(false)}
|
||||||
>
|
>
|
||||||
关闭
|
关闭
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
{!!usableTickets.length && !ticketLoading && (
|
||||||
|
<View className="text-xs text-gray-500 mb-2">
|
||||||
|
<Text>可用合计 {availableTicketTotal} 张;下单时将优先使用可用数量少的水票</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{ticketLoading ? (
|
{ticketLoading ? (
|
||||||
<View className="py-10 text-center text-gray-500">
|
<View className="py-10 text-center text-gray-500">
|
||||||
<Text>加载中...</Text>
|
<Text>加载中...</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{!!usableTickets.length ? (
|
{!!usableTickets.length ? (
|
||||||
<CellGroup>
|
<CellGroup>
|
||||||
{usableTickets.map((t) => {
|
{ticketsToConsume.map((t) => {
|
||||||
const active = selectedTicket?.id && Number(selectedTicket.id) === Number(t.id)
|
return (
|
||||||
return (
|
<Cell
|
||||||
<Cell
|
key={t.id}
|
||||||
key={t.id}
|
title={<Text>票号 {t.id}</Text>}
|
||||||
title={<Text className={active ? 'text-green-600' : ''}>票号 {t.id}</Text>}
|
description={t.orderNo ? `来源订单:${t.orderNo}` : ''}
|
||||||
description={t.orderNo ? `来源订单:${t.orderNo}` : ''}
|
extra={<Text className="text-gray-700">可用 {getTicketAvailableQty(t)}</Text>}
|
||||||
extra={<Text className="text-gray-700">可用 {t.availableQty ?? 0}</Text>}
|
onClick={() => setTicketPopupVisible(false)}
|
||||||
onClick={() => {
|
/>
|
||||||
setSelectedTicketId(Number(t.id))
|
)
|
||||||
setTicketPopupVisible(false)
|
})}
|
||||||
Taro.showToast({ title: '水票已选择', icon: 'success' })
|
</CellGroup>
|
||||||
}}
|
) : (
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</CellGroup>
|
|
||||||
) : (
|
|
||||||
<View className="py-10 text-center">
|
<View className="py-10 text-center">
|
||||||
<Empty description="暂无可用水票" />
|
<Empty description="暂无可用水票" />
|
||||||
<View className="mt-4 flex justify-center">
|
<View className="mt-4 flex justify-center">
|
||||||
@@ -890,11 +948,11 @@ const OrderConfirm = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</Popup>
|
</Popup>
|
||||||
|
|
||||||
{/* 门店选择弹窗 */}
|
{/* 门店选择弹窗 */}
|
||||||
<Popup
|
<Popup
|
||||||
@@ -973,19 +1031,18 @@ const OrderConfirm = () => {
|
|||||||
去购买水票
|
去购买水票
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
type="success"
|
type="success"
|
||||||
size="large"
|
size="large"
|
||||||
loading={submitLoading || deliveryRangeChecking}
|
loading={submitLoading || deliveryRangeChecking}
|
||||||
disabled={
|
disabled={
|
||||||
deliveryRangeChecking ||
|
deliveryRangeChecking ||
|
||||||
inDeliveryRange === false ||
|
inDeliveryRange === false ||
|
||||||
!selectedTicket?.id ||
|
availableTicketTotal <= 0 ||
|
||||||
availableTicketTotal <= 0 ||
|
!canStartOrder
|
||||||
!canStartOrder
|
}
|
||||||
}
|
onClick={onSubmit}
|
||||||
onClick={onSubmit}
|
>
|
||||||
>
|
|
||||||
{deliveryRangeChecking
|
{deliveryRangeChecking
|
||||||
? '校验配送范围...'
|
? '校验配送范围...'
|
||||||
: (inDeliveryRange === false ? '不在配送范围' : (submitLoading ? '提交中...' : '立即提交'))
|
: (inDeliveryRange === false ? '不在配送范围' : (submitLoading ? '提交中...' : '立即提交'))
|
||||||
|
|||||||
Reference in New Issue
Block a user