forked from gxwebsoft/mp-10550
- 优化导入路径,修复 PageParam 类型引用 - 新增 DeliverConfirmMode 类型定义,支持拍照完成和等待客户确认两种模式 - 实现配送确认的双模式功能,支持直接完成和等待确认流程 - 重构订单状态判断逻辑,完善配送流程状态管理 - 新增用户端确认收货功能,支持手动确认收货操作 - 优化订单列表展示,增加票号、取货点、门店电话等详细信息 - 添加地址复制和联系门店功能按钮 - 实现补传照片完成订单功能 - 更新订单流程状态显示,提供更准确的状态标识 - 添加配送确认模式切换的单选框界面 - 优化下单成功后的页面跳转逻辑 - 新增水票配送订单后端接口设计文档
633 lines
21 KiB
TypeScript
633 lines
21 KiB
TypeScript
import { useEffect, useMemo, useRef, useState } from 'react'
|
||
import Taro, { useDidShow } from '@tarojs/taro'
|
||
import { View, Text } from '@tarojs/components'
|
||
import {
|
||
Button,
|
||
Cell,
|
||
CellGroup,
|
||
ConfigProvider,
|
||
DatePicker,
|
||
Input,
|
||
InputNumber,
|
||
Popup,
|
||
Space
|
||
} from '@nutui/nutui-react-taro'
|
||
import { ArrowRight, Location, Shop, Ticket } from '@nutui/icons-react-taro'
|
||
import dayjs from 'dayjs'
|
||
import type { ShopGoods } from '@/api/shop/shopGoods/model'
|
||
import { getShopGoods } from '@/api/shop/shopGoods'
|
||
import { listShopUserAddress } from '@/api/shop/shopUserAddress'
|
||
import type { ShopUserAddress } from '@/api/shop/shopUserAddress/model'
|
||
import './use.scss'
|
||
import Gap from "@/components/Gap";
|
||
import OrderConfirmSkeleton from "@/components/OrderConfirmSkeleton";
|
||
import type {ShopStore} from "@/api/shop/shopStore/model";
|
||
import {getShopStore, listShopStore} from "@/api/shop/shopStore";
|
||
import {getSelectedStoreFromStorage, saveSelectedStoreToStorage} from "@/utils/storeSelection";
|
||
import type { GltUserTicket } from '@/api/glt/gltUserTicket/model'
|
||
import { listGltUserTicket } from '@/api/glt/gltUserTicket'
|
||
import { addGltTicketOrder } from '@/api/glt/gltTicketOrder'
|
||
|
||
const MIN_START_QTY = 10
|
||
|
||
const OrderConfirm = () => {
|
||
const [goods, setGoods] = useState<ShopGoods | null>(null);
|
||
const [address, setAddress] = useState<ShopUserAddress>()
|
||
const [quantity, setQuantity] = useState<number>(MIN_START_QTY)
|
||
const [orderRemark, setOrderRemark] = useState<string>('')
|
||
const [sendTime, setSendTime] = useState<Date>(new Date())
|
||
const [sendTimePickerVisible, setSendTimePickerVisible] = useState(false)
|
||
const [loading, setLoading] = useState<boolean>(true)
|
||
const [error, setError] = useState<string>('')
|
||
const [submitLoading, setSubmitLoading] = useState<boolean>(false)
|
||
const loadAllDataLoadingRef = useRef(false)
|
||
const hasInitialLoadedRef = useRef(false)
|
||
|
||
// InputNumber 主题配置
|
||
const customTheme = {
|
||
nutuiInputnumberButtonWidth: '28px',
|
||
nutuiInputnumberButtonHeight: '28px',
|
||
nutuiInputnumberInputWidth: '40px',
|
||
nutuiInputnumberInputHeight: '28px',
|
||
nutuiInputnumberInputBorderRadius: '4px',
|
||
nutuiInputnumberButtonBorderRadius: '4px',
|
||
}
|
||
|
||
// 门店选择:用于在下单页展示当前“已选门店”,并允许用户切换(写入 SelectedStore Storage)
|
||
const [storePopupVisible, setStorePopupVisible] = useState(false)
|
||
const [stores, setStores] = useState<ShopStore[]>([])
|
||
const [storeLoading, setStoreLoading] = useState(false)
|
||
const [selectedStore, setSelectedStore] = useState<ShopStore | null>(getSelectedStoreFromStorage())
|
||
|
||
// 水票:用于“立即送水”下单(用水票抵扣,无需支付)
|
||
const [tickets, setTickets] = useState<GltUserTicket[]>([])
|
||
const [selectedTicketId, setSelectedTicketId] = useState<number | undefined>(undefined)
|
||
const [ticketPopupVisible, setTicketPopupVisible] = useState(false)
|
||
const [ticketLoading, setTicketLoading] = useState(false)
|
||
|
||
const router = Taro.getCurrentInstance().router;
|
||
const goodsId = router?.params?.goodsId;
|
||
const numericGoodsId = useMemo(() => {
|
||
const n = goodsId ? Number(goodsId) : undefined
|
||
return typeof n === 'number' && Number.isFinite(n) ? n : undefined
|
||
}, [goodsId])
|
||
|
||
const userId = useMemo(() => {
|
||
const raw = Taro.getStorageSync('UserId')
|
||
const id = Number(raw)
|
||
return Number.isFinite(id) && id > 0 ? id : undefined
|
||
}, [])
|
||
|
||
const usableTickets = useMemo(() => {
|
||
const list = (tickets || [])
|
||
.filter(t => t?.deleted !== 1)
|
||
.filter(t => t?.status !== 1)
|
||
.filter(t => Number.isFinite(Number(t?.id)) && Number(t.id) > 0)
|
||
.filter(t => (t.availableQty ?? 0) > 0)
|
||
// Some tenants don't fill goodsId on ticket; allow it as a fallback.
|
||
.filter(t => (numericGoodsId ? (!t.goodsId || t.goodsId === numericGoodsId) : true))
|
||
// FIFO: use older tickets first (reduce disputes).
|
||
return list.sort((a, b) => {
|
||
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)
|
||
})
|
||
}, [tickets, numericGoodsId])
|
||
|
||
const selectedTicket = useMemo(() => {
|
||
if (!selectedTicketId) return undefined
|
||
return usableTickets.find(t => Number(t.id) === Number(selectedTicketId))
|
||
}, [usableTickets, selectedTicketId])
|
||
|
||
const availableTicketTotal = useMemo(() => {
|
||
return Number(selectedTicket?.availableQty || 0)
|
||
}, [selectedTicket?.availableQty])
|
||
|
||
const maxQuantity = useMemo(() => {
|
||
const stockMax = goods?.stock ?? 999
|
||
return Math.max(0, Math.min(stockMax, availableTicketTotal))
|
||
}, [availableTicketTotal, goods?.stock])
|
||
|
||
const canStartOrder = useMemo(() => {
|
||
return maxQuantity >= MIN_START_QTY
|
||
}, [maxQuantity])
|
||
|
||
const displayQty = useMemo(() => {
|
||
if (!canStartOrder) return 0
|
||
return Math.max(MIN_START_QTY, Math.min(quantity, maxQuantity))
|
||
}, [quantity, maxQuantity, canStartOrder])
|
||
|
||
const sendTimeText = useMemo(() => {
|
||
return dayjs(sendTime).format('YYYY-MM-DD HH:mm')
|
||
}, [sendTime])
|
||
|
||
const loadStores = async () => {
|
||
if (storeLoading) return
|
||
try {
|
||
setStoreLoading(true)
|
||
const list = await listShopStore()
|
||
setStores((list || []).filter(s => s?.isDelete !== 1))
|
||
} catch (e) {
|
||
console.error('获取门店列表失败:', e)
|
||
setStores([])
|
||
Taro.showToast({title: '获取门店列表失败', icon: 'none'})
|
||
} finally {
|
||
setStoreLoading(false)
|
||
}
|
||
}
|
||
|
||
const openStorePopup = async () => {
|
||
setStorePopupVisible(true)
|
||
if (!stores.length) {
|
||
await loadStores()
|
||
}
|
||
}
|
||
|
||
// 处理数量变化
|
||
const handleQuantityChange = (value: string | number) => {
|
||
const parsed = typeof value === 'string' ? parseInt(value) : value
|
||
const newQuantity = Number.isFinite(parsed) ? Number(parsed) : 0
|
||
const upper = maxQuantity
|
||
if (!canStartOrder || upper <= 0) {
|
||
setQuantity(0)
|
||
return
|
||
}
|
||
setQuantity(Math.max(MIN_START_QTY, Math.min(newQuantity || MIN_START_QTY, upper)))
|
||
}
|
||
|
||
const loadUserTickets = async () => {
|
||
if (ticketLoading) return
|
||
if (!userId) {
|
||
setTickets([])
|
||
return
|
||
}
|
||
try {
|
||
setTicketLoading(true)
|
||
const list = await listGltUserTicket({ userId, status: 0 })
|
||
setTickets(list || [])
|
||
} catch (e) {
|
||
console.error('获取水票失败:', e)
|
||
setTickets([])
|
||
Taro.showToast({ title: '获取水票失败', icon: 'none' })
|
||
} finally {
|
||
setTicketLoading(false)
|
||
}
|
||
}
|
||
|
||
const onSubmit = async () => {
|
||
if (submitLoading) return
|
||
if (!goods?.goodsId) return
|
||
|
||
// 基础校验
|
||
if (!userId) {
|
||
Taro.showToast({ title: '请先登录', icon: 'none' })
|
||
return
|
||
}
|
||
if (!selectedStore?.id) {
|
||
Taro.showToast({ title: '请选择门店', icon: 'none' })
|
||
return
|
||
}
|
||
if (!address?.id) {
|
||
Taro.showToast({ title: '请选择收货地址', icon: 'none' })
|
||
return
|
||
}
|
||
if (!selectedTicket?.id) {
|
||
Taro.showToast({ title: '请选择水票', icon: 'none' })
|
||
return
|
||
}
|
||
if (availableTicketTotal <= 0) {
|
||
Taro.showToast({ title: '暂无可用水票', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
const finalQty = displayQty
|
||
if (finalQty <= 0) {
|
||
Taro.showToast({ title: '请选择送水数量', icon: 'none' })
|
||
return
|
||
}
|
||
if (finalQty > availableTicketTotal) {
|
||
Taro.showToast({ title: '水票可用次数不足', icon: 'none' })
|
||
return
|
||
}
|
||
if (goods.stock !== undefined && finalQty > goods.stock) {
|
||
Taro.showToast({ title: '商品库存不足', icon: 'none' })
|
||
return
|
||
}
|
||
if (finalQty < MIN_START_QTY) {
|
||
Taro.showToast({ title: `最低起送 ${MIN_START_QTY} 桶`, icon: 'none' })
|
||
return
|
||
}
|
||
if (!sendTime) {
|
||
Taro.showToast({ title: '请选择配送时间', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
const confirmRes = await Taro.showModal({
|
||
title: '确认下单',
|
||
content: `配送时间:${sendTimeText}\n将使用 ${finalQty} 张水票下单,送水 ${finalQty} 桶,是否确认?`
|
||
})
|
||
if (!confirmRes.confirm) return
|
||
|
||
try {
|
||
setSubmitLoading(true)
|
||
Taro.showLoading({ title: '提交中...' })
|
||
|
||
await addGltTicketOrder({
|
||
userTicketId: selectedTicket.id,
|
||
storeId: selectedStore.id,
|
||
addressId: address.id,
|
||
totalNum: finalQty,
|
||
buyerRemarks: orderRemark,
|
||
sendTime: dayjs(sendTime).format('YYYY-MM-DD HH:mm:ss'),
|
||
// Backend may take userId from token; pass-through is harmless if backend ignores it.
|
||
userId,
|
||
comments: goods.name ? `立即送水:${goods.name}` : '立即送水'
|
||
})
|
||
|
||
await loadUserTickets()
|
||
|
||
Taro.showToast({ title: '下单成功', icon: 'success' })
|
||
setTimeout(() => {
|
||
// 跳转到“我的送水订单”
|
||
Taro.redirectTo({ url: '/user/ticket/index?tab=order' })
|
||
}, 800)
|
||
} catch (e: any) {
|
||
console.error('水票下单失败:', e)
|
||
Taro.showToast({ title: e?.message || '下单失败', icon: 'none' })
|
||
} finally {
|
||
Taro.hideLoading()
|
||
setSubmitLoading(false)
|
||
}
|
||
}
|
||
|
||
// 统一的数据加载函数
|
||
const loadAllData = async (opts?: { silent?: boolean }) => {
|
||
if (loadAllDataLoadingRef.current) return
|
||
loadAllDataLoadingRef.current = true
|
||
try {
|
||
if (!opts?.silent) setLoading(true)
|
||
setError('')
|
||
|
||
const [goodsRes, addressRes] = await Promise.all([
|
||
numericGoodsId ? getShopGoods(numericGoodsId) : Promise.resolve(null),
|
||
listShopUserAddress({ isDefault: true })
|
||
])
|
||
|
||
// 设置商品信息
|
||
if (goodsRes) {
|
||
setGoods(goodsRes)
|
||
hasInitialLoadedRef.current = true
|
||
}
|
||
|
||
// 设置默认收货地址
|
||
if (addressRes && addressRes.length > 0) {
|
||
setAddress(addressRes[0])
|
||
}
|
||
// Tickets are non-blocking for first paint; load in background.
|
||
loadUserTickets()
|
||
} catch (err) {
|
||
console.error('加载数据失败:', err)
|
||
if (opts?.silent) {
|
||
Taro.showToast({ title: '刷新失败,请稍后重试', icon: 'none' })
|
||
} else {
|
||
setError('加载数据失败,请重试')
|
||
}
|
||
} finally {
|
||
if (!opts?.silent) setLoading(false)
|
||
loadAllDataLoadingRef.current = false
|
||
}
|
||
}
|
||
|
||
useDidShow(() => {
|
||
// 返回/切换到该页面时,刷新一下当前已选门店
|
||
setSelectedStore(getSelectedStoreFromStorage())
|
||
loadAllData({ silent: hasInitialLoadedRef.current })
|
||
})
|
||
|
||
// When tickets/stock change, clamp quantity into [0..maxQuantity].
|
||
useEffect(() => {
|
||
setQuantity(prev => {
|
||
if (maxQuantity <= 0) return 0
|
||
if (maxQuantity < MIN_START_QTY) return 0
|
||
if (!prev || prev < MIN_START_QTY) return MIN_START_QTY
|
||
return Math.min(prev, 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])
|
||
|
||
// 重新加载数据
|
||
const handleRetry = () => {
|
||
loadAllData()
|
||
}
|
||
|
||
// 错误状态
|
||
if (error) {
|
||
return (
|
||
<View className="order-confirm-page">
|
||
<View className="error-state">
|
||
<Text className="error-text">{error}</Text>
|
||
<Button onClick={handleRetry}>重新加载</Button>
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
// 加载状态
|
||
if (loading || !goods) {
|
||
return <OrderConfirmSkeleton/>
|
||
}
|
||
|
||
return (
|
||
<div className={'order-confirm-page'}>
|
||
<CellGroup>
|
||
<Cell
|
||
title={(
|
||
<View className="flex items-center gap-2">
|
||
<Shop className={'text-gray-500'}/>
|
||
<Text>选择门店</Text>
|
||
</View>
|
||
)}
|
||
extra={(
|
||
<View className={'flex items-center gap-2'}>
|
||
<View className={'text-gray-900'}>
|
||
{selectedStore?.name || '请选择门店'}
|
||
</View>
|
||
<ArrowRight className={'text-gray-400'} size={14}/>
|
||
</View>
|
||
)}
|
||
onClick={openStorePopup}
|
||
/>
|
||
</CellGroup>
|
||
<CellGroup>
|
||
{
|
||
address && (
|
||
<Cell className={'address-bottom-line'} onClick={() => Taro.navigateTo({url: '/user/address/index'})}>
|
||
<Space>
|
||
<Location className={'text-gray-500'}/>
|
||
<View className={'flex flex-col w-full justify-between items-start'}>
|
||
<Space className={'flex flex-row w-full'}>
|
||
<View className={'flex-wrap text-nowrap whitespace-nowrap text-gray-500'}>送至</View>
|
||
<View className={'font-medium text-sm flex items-center w-full'}>
|
||
<View
|
||
style={{width: '64%'}}>{address.province} {address.city} {address.region} {address.address}</View>
|
||
<ArrowRight className={'text-gray-400'} size={14}/>
|
||
</View>
|
||
</Space>
|
||
<View className={'pt-1 pb-3 text-gray-500'}>{address.name} {address.phone}</View>
|
||
</View>
|
||
</Space>
|
||
</Cell>
|
||
)
|
||
}
|
||
{!address && (
|
||
<Cell className={''} onClick={() => Taro.navigateTo({url: '/user/address/index'})}>
|
||
<Space>
|
||
<Location/>
|
||
添加收货地址
|
||
</Space>
|
||
</Cell>
|
||
)}
|
||
</CellGroup>
|
||
|
||
<CellGroup>
|
||
<Cell
|
||
title={'配送时间'}
|
||
extra={(
|
||
<View className={'flex items-center gap-2'}>
|
||
<View className={'text-gray-900'}>{sendTimeText}</View>
|
||
<ArrowRight className={'text-gray-400'} size={14}/>
|
||
</View>
|
||
)}
|
||
onClick={() => setSendTimePickerVisible(true)}
|
||
/>
|
||
</CellGroup>
|
||
|
||
<CellGroup>
|
||
<Cell
|
||
title={'送水数量'}
|
||
description={
|
||
canStartOrder
|
||
? `最低起送 ${MIN_START_QTY} 桶`
|
||
: `最低起送 ${MIN_START_QTY} 桶(当前最多 ${maxQuantity} 桶)`
|
||
}
|
||
extra={(
|
||
<ConfigProvider theme={customTheme}>
|
||
<InputNumber
|
||
value={displayQty}
|
||
min={canStartOrder ? MIN_START_QTY : 0}
|
||
max={canStartOrder ? maxQuantity : 0}
|
||
disabled={!canStartOrder}
|
||
onChange={handleQuantityChange}
|
||
/>
|
||
</ConfigProvider>
|
||
)}
|
||
/>
|
||
</CellGroup>
|
||
|
||
<CellGroup>
|
||
<Cell
|
||
title={(
|
||
<View className="flex items-center gap-2">
|
||
<Ticket className={'text-gray-500'}/>
|
||
<Text>选择水票</Text>
|
||
</View>
|
||
)}
|
||
extra={(
|
||
<View className={'flex items-center gap-2'}>
|
||
<View className={'text-gray-900'}>
|
||
{ticketLoading
|
||
? '加载中...'
|
||
: (selectedTicket
|
||
? `${selectedTicket.templateName || '水票'}(可用${selectedTicket.availableQty ?? 0})`
|
||
: '请选择'
|
||
)
|
||
}
|
||
</View>
|
||
<ArrowRight className={'text-gray-400'} size={14}/>
|
||
</View>
|
||
)}
|
||
onClick={() => !ticketLoading && setTicketPopupVisible(true)}
|
||
/>
|
||
<Cell
|
||
title={'本次使用'}
|
||
extra={<View className={'font-medium'}>{displayQty} 张</View>}
|
||
/>
|
||
</CellGroup>
|
||
|
||
<CellGroup>
|
||
<Cell title={'备注'} extra={(
|
||
<Input
|
||
placeholder={'(选填)请填写备注'}
|
||
style={{padding: '0'}}
|
||
value={orderRemark}
|
||
onChange={(value) => setOrderRemark(value)}
|
||
maxLength={100}
|
||
/>
|
||
)}/>
|
||
</CellGroup>
|
||
|
||
{/* 水票明细弹窗 */}
|
||
<Popup
|
||
visible={ticketPopupVisible}
|
||
position="bottom"
|
||
style={{ height: '70vh' }}
|
||
onClose={() => setTicketPopupVisible(false)}
|
||
>
|
||
<View className="p-4">
|
||
<View className="flex justify-between items-center mb-3">
|
||
<Text className="text-base font-medium">水票明细</Text>
|
||
<Text
|
||
className="text-sm text-gray-500"
|
||
onClick={() => setTicketPopupVisible(false)}
|
||
>
|
||
关闭
|
||
</Text>
|
||
</View>
|
||
|
||
{ticketLoading ? (
|
||
<View className="py-10 text-center text-gray-500">
|
||
<Text>加载中...</Text>
|
||
</View>
|
||
) : (
|
||
<CellGroup>
|
||
{usableTickets.map((t) => {
|
||
const active = selectedTicket?.id && Number(selectedTicket.id) === Number(t.id)
|
||
return (
|
||
<Cell
|
||
key={t.id}
|
||
title={<Text className={active ? 'text-green-600' : ''}>{t.templateName || '水票'}</Text>}
|
||
description={t.orderNo ? `来源订单:${t.orderNo}` : ''}
|
||
extra={<Text className="text-gray-700">可用 {t.availableQty ?? 0}</Text>}
|
||
onClick={() => {
|
||
setSelectedTicketId(Number(t.id))
|
||
setTicketPopupVisible(false)
|
||
Taro.showToast({ title: '水票已选择', icon: 'success' })
|
||
}}
|
||
/>
|
||
)})}
|
||
{!usableTickets.length && (
|
||
<Cell title={<Text className="text-gray-500">暂无可用水票</Text>} />
|
||
)}
|
||
</CellGroup>
|
||
)}
|
||
</View>
|
||
</Popup>
|
||
|
||
{/* 门店选择弹窗 */}
|
||
<Popup
|
||
visible={storePopupVisible}
|
||
position="bottom"
|
||
style={{height: '70vh'}}
|
||
onClose={() => setStorePopupVisible(false)}
|
||
>
|
||
<View className="p-4">
|
||
<View className="flex justify-between items-center mb-3">
|
||
<Text className="text-base font-medium">选择门店</Text>
|
||
<Text
|
||
className="text-sm text-gray-500"
|
||
onClick={() => setStorePopupVisible(false)}
|
||
>
|
||
关闭
|
||
</Text>
|
||
</View>
|
||
|
||
{storeLoading ? (
|
||
<View className="py-10 text-center text-gray-500">
|
||
<Text>加载中...</Text>
|
||
</View>
|
||
) : (
|
||
<CellGroup>
|
||
{stores.map((s) => {
|
||
const isActive = !!selectedStore?.id && selectedStore.id === s.id
|
||
return (
|
||
<Cell
|
||
key={s.id}
|
||
title={<Text className={isActive ? 'text-green-600' : ''}>{s.name || `门店${s.id}`}</Text>}
|
||
description={s.address || ''}
|
||
onClick={async () => {
|
||
let storeToSave: ShopStore = s
|
||
if (s?.id) {
|
||
try {
|
||
const full = await getShopStore(s.id)
|
||
if (full) storeToSave = full
|
||
} catch (_e) {
|
||
// keep base item
|
||
}
|
||
}
|
||
setSelectedStore(storeToSave)
|
||
saveSelectedStoreToStorage(storeToSave)
|
||
setStorePopupVisible(false)
|
||
Taro.showToast({title: '门店已切换', icon: 'success'})
|
||
}}
|
||
/>
|
||
)
|
||
})}
|
||
{!stores.length && (
|
||
<Cell title={<Text className="text-gray-500">暂无门店数据</Text>} />
|
||
)}
|
||
</CellGroup>
|
||
)}
|
||
</View>
|
||
</Popup>
|
||
|
||
<Gap height={50}/>
|
||
|
||
<DatePicker
|
||
visible={sendTimePickerVisible}
|
||
title="选择配送时间"
|
||
type="datetime"
|
||
startDate={new Date()}
|
||
endDate={dayjs().add(30, 'day').toDate()}
|
||
value={sendTime}
|
||
onClose={() => setSendTimePickerVisible(false)}
|
||
onCancel={() => setSendTimePickerVisible(false)}
|
||
onConfirm={(_options, selectedValue) => {
|
||
const [y, m, d, hh, mm] = (selectedValue || []).map(v => Number(v))
|
||
const next = new Date(y, (m || 1) - 1, d || 1, hh || 0, mm || 0)
|
||
setSendTime(next)
|
||
setSendTimePickerVisible(false)
|
||
}}
|
||
/>
|
||
|
||
<div className={'fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-10 border-t border-gray-200'}>
|
||
<View className={'btn-bar flex justify-between items-center'}>
|
||
<div className={'flex flex-col justify-center items-start mx-4'}>
|
||
<View className={'flex items-center gap-2'}>
|
||
<span className={'total-price text-sm text-gray-500'}>使用水票:</span>
|
||
<span className={'text-red-500 text-xl font-bold'}>
|
||
{displayQty}
|
||
</span>
|
||
<span className={'text-sm text-gray-500'}>张</span>
|
||
</View>
|
||
</div>
|
||
<div className={'buy-btn mx-4'}>
|
||
<Button
|
||
type="success"
|
||
size="large"
|
||
loading={submitLoading}
|
||
disabled={!selectedTicket?.id || availableTicketTotal <= 0 || !canStartOrder}
|
||
onClick={onSubmit}
|
||
>
|
||
{submitLoading ? '提交中...' : '立即提交'}
|
||
</Button>
|
||
</div>
|
||
</View>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default OrderConfirm;
|