forked from gxwebsoft/mp-10550
feat(order): 迁移和完善配送方式功能,支持全链路入库与展示
- 迁移配送方式选择功能从 orderConfirm 页至 user/ticket/use 页面 - orderConfirm 页面移除配送方式相关状态、UI与校验,取消配送费计算 - user/ticket/use 页面新增配送方式UI组件,支持配送费计算、楼层选择弹窗和提交校验 - 新增录入deliveryMethod、deliveryFloor、deliveryFee字段至订单模型与后端数据库 - 骑手端订单列表新增配送方式、楼层、配送费的详细展示 - 更新环境配置接口地址到正式API,修正测试及开发环境 - 用户页底部组件UI优化,新增版权icon并重构结构样式 - 使用配送方式字段校验下单逻辑,支持编辑模式配送信息回显与费用显示 - 移除orderConfirm中配送方式相关样式和组件,实现代码回滚清理
This commit is contained in:
@@ -89,6 +89,22 @@ const OrderConfirm = () => {
|
||||
// Prevent using stale `inDeliveryRange` from a previous address when user switches addresses.
|
||||
const [deliveryRangeCheckedAddressId, setDeliveryRangeCheckedAddressId] = useState<number | undefined>(undefined)
|
||||
|
||||
// 配送方式:elevator(电梯) / stairs(步梯) / groundFloor(一楼商铺/其他)
|
||||
const [deliveryMethod, setDeliveryMethod] = useState<string>('')
|
||||
// 步梯是否需要送上楼(null=未选择)
|
||||
const [needCarryUpstairs, setNeedCarryUpstairs] = useState<boolean | null>(null)
|
||||
// 楼层(从2开始,需要送上楼时选择)
|
||||
const [deliveryFloor, setDeliveryFloor] = useState<number>(2)
|
||||
// 楼层选择弹窗
|
||||
const [floorPickerVisible, setFloorPickerVisible] = useState(false)
|
||||
|
||||
// 计算配送费:每桶每层1元,第1层不收费
|
||||
const getDeliveryFee = () => {
|
||||
if (deliveryMethod !== 'stairs' || !needCarryUpstairs) return 0
|
||||
if (deliveryFloor <= 1) return 0
|
||||
return displayQty * (deliveryFloor - 1)
|
||||
}
|
||||
|
||||
const router = Taro.getCurrentInstance().router;
|
||||
const goodsId = router?.params?.goodsId;
|
||||
const orderId = router?.params?.orderId;
|
||||
@@ -594,6 +610,19 @@ const OrderConfirm = () => {
|
||||
Taro.showToast({ title: '请选择收货地址', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 配送方式校验(必选)
|
||||
if (!deliveryMethod) {
|
||||
Taro.showToast({ title: '请选择配送方式', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 步梯场景:必须选择是否送上楼
|
||||
if (deliveryMethod === 'stairs' && needCarryUpstairs === null) {
|
||||
Taro.showToast({ title: '请选择是否需要送上楼', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
if (!addressHasCoords) {
|
||||
Taro.showToast({ title: '该收货地址缺少经纬度,请在地址里选择地图定位后重试', icon: 'none' })
|
||||
return
|
||||
@@ -653,11 +682,14 @@ const OrderConfirm = () => {
|
||||
const ok = await ensureInDeliveryRange()
|
||||
if (!ok) return
|
||||
|
||||
const deliveryFee = getDeliveryFee()
|
||||
const confirmContent = isEditMode
|
||||
? `配送时间:${sendTimeText}\n送水数量:${finalQty} 桶\n配送方式:${deliveryMethod === 'elevator' ? '电梯' : deliveryMethod === 'stairs' ? '步梯' : '一楼商铺/其他'}${deliveryMethod === 'stairs' && needCarryUpstairs && deliveryFloor > 1 ? `(${deliveryFloor}楼)` : deliveryMethod === 'stairs' && !needCarryUpstairs ? '(不送上楼)' : ''}\n${deliveryFee > 0 ? `配送费:¥${deliveryFee.toFixed(2)}\n` : ''}是否确认修改?`
|
||||
: `配送时间:${sendTimeText}\n配送方式:${deliveryMethod === 'elevator' ? '电梯' : deliveryMethod === 'stairs' ? '步梯' : '一楼商铺/其他'}${deliveryMethod === 'stairs' && needCarryUpstairs && deliveryFloor > 1 ? `(${deliveryFloor}楼)` : deliveryMethod === 'stairs' && !needCarryUpstairs ? '(不送上楼)' : ''}\n${deliveryFee > 0 ? `配送费:¥${deliveryFee.toFixed(2)}\n` : ''}将使用 ${finalQty} 张水票下单(优先使用可用数量少的水票),送水 ${finalQty} 桶,是否确认?`
|
||||
|
||||
const confirmRes = await Taro.showModal({
|
||||
title: isEditMode ? '确认修改' : '确认下单',
|
||||
content: isEditMode
|
||||
? `配送时间:${sendTimeText}\n送水数量:${finalQty} 桶\n是否确认修改?`
|
||||
: `配送时间:${sendTimeText}\n将使用 ${finalQty} 张水票下单(优先使用可用数量少的水票),送水 ${finalQty} 桶,是否确认?`
|
||||
content: confirmContent
|
||||
})
|
||||
if (!confirmRes.confirm) return
|
||||
|
||||
@@ -671,7 +703,10 @@ const OrderConfirm = () => {
|
||||
addressId: address.id,
|
||||
totalNum: finalQty,
|
||||
buyerRemarks: orderRemark,
|
||||
sendTime: dayjs(sendTime).startOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||
sendTime: dayjs(sendTime).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
||||
deliveryMethod,
|
||||
deliveryFloor: deliveryMethod === 'stairs' && needCarryUpstairs ? deliveryFloor : undefined,
|
||||
deliveryFee: getDeliveryFee() || undefined
|
||||
})
|
||||
} else {
|
||||
// Best-effort auto dispatch rider. If it fails, backend/manual dispatch can still handle it.
|
||||
@@ -697,7 +732,11 @@ const OrderConfirm = () => {
|
||||
riderId: Number.isFinite(Number(autoRider?.userId)) ? Number(autoRider?.userId) : undefined,
|
||||
riderName: autoRider?.realName,
|
||||
riderPhone: autoRider?.mobile,
|
||||
comments: goods?.name ? `立即送水:${goods.name}` : '立即送水'
|
||||
comments: goods?.name ? `立即送水:${goods.name}` : '立即送水',
|
||||
// 配送方式信息
|
||||
deliveryMethod,
|
||||
deliveryFloor: deliveryMethod === 'stairs' && needCarryUpstairs ? deliveryFloor : undefined,
|
||||
deliveryFee: getDeliveryFee() || undefined
|
||||
})
|
||||
remain -= useQty
|
||||
}
|
||||
@@ -785,6 +824,16 @@ const OrderConfirm = () => {
|
||||
const st = parseTime(editingOrderRes.sendTime)
|
||||
if (st) setSendTime(clampSendDateToToday(st).toDate())
|
||||
|
||||
// 回显配送方式
|
||||
if (editingOrderRes.deliveryMethod) {
|
||||
setDeliveryMethod(editingOrderRes.deliveryMethod)
|
||||
if (editingOrderRes.deliveryMethod === 'stairs') {
|
||||
const hasFloor = editingOrderRes.deliveryFloor && editingOrderRes.deliveryFloor > 1
|
||||
setNeedCarryUpstairs(hasFloor)
|
||||
if (hasFloor) setDeliveryFloor(editingOrderRes.deliveryFloor)
|
||||
}
|
||||
}
|
||||
|
||||
const addrId = Number(editingOrderRes.addressId)
|
||||
const addrIdSafe = Number.isFinite(addrId) && addrId > 0 ? addrId : undefined
|
||||
if (addrIdSafe) {
|
||||
@@ -1051,6 +1100,85 @@ const OrderConfirm = () => {
|
||||
)}
|
||||
</CellGroup>
|
||||
|
||||
{/* 配送方式选择(必选) */}
|
||||
<CellGroup className={'delivery-method-group'}>
|
||||
<Cell>
|
||||
<View className={'delivery-method-section'}>
|
||||
<View className={'delivery-method-label'}>
|
||||
<Text className={'font-medium text-sm'}>配送方式</Text>
|
||||
<Text className={'text-red-500 text-xs ml-1'}>*</Text>
|
||||
</View>
|
||||
<View className={'delivery-method-options'}>
|
||||
{[
|
||||
{ key: 'elevator', label: '电梯', icon: '🏛️' },
|
||||
{ key: 'stairs', label: '步梯', icon: '🚶' },
|
||||
{ key: 'groundFloor', label: '一楼商铺/其他', icon: '🏪' },
|
||||
].map(item => (
|
||||
<View
|
||||
key={item.key}
|
||||
className={`delivery-method-item ${deliveryMethod === item.key ? 'active' : ''}`}
|
||||
onClick={() => {
|
||||
setDeliveryMethod(item.key)
|
||||
setNeedCarryUpstairs(null)
|
||||
setDeliveryFloor(2)
|
||||
}}
|
||||
>
|
||||
<Text className={'delivery-method-icon'}>{item.icon}</Text>
|
||||
<Text className={'text-sm'}>{item.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 步梯:是否需要送上楼 */}
|
||||
{deliveryMethod === 'stairs' && (
|
||||
<View className={'carry-upstairs-section'}>
|
||||
<Text className={'text-sm text-gray-600 mb-2'}>是否需要送上楼?</Text>
|
||||
<View className={'carry-upstairs-options'}>
|
||||
<View
|
||||
className={`carry-upstairs-item ${needCarryUpstairs === true ? 'active' : ''}`}
|
||||
onClick={() => setNeedCarryUpstairs(true)}
|
||||
>
|
||||
<Text>需要送上楼</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`carry-upstairs-item ${needCarryUpstairs === false ? 'active' : ''}`}
|
||||
onClick={() => {
|
||||
setNeedCarryUpstairs(false)
|
||||
setDeliveryFloor(2)
|
||||
}}
|
||||
>
|
||||
<Text>不需要</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 步梯+送上楼:选择楼层 */}
|
||||
{deliveryMethod === 'stairs' && needCarryUpstairs === true && (
|
||||
<View className={'floor-select-section'}>
|
||||
<Text className={'text-sm text-gray-600'}>送至楼层</Text>
|
||||
<View
|
||||
className={'floor-select-btn'}
|
||||
onClick={() => setFloorPickerVisible(true)}
|
||||
>
|
||||
<Text className={deliveryFloor > 1 ? 'text-gray-900' : 'text-gray-400'}>
|
||||
{deliveryFloor > 1 ? `${deliveryFloor}楼` : '请选择楼层'}
|
||||
</Text>
|
||||
<ArrowRight className={'text-gray-400'} size={14}/>
|
||||
</View>
|
||||
{deliveryFloor > 1 && (
|
||||
<View className={'floor-fee-tip'}>
|
||||
<Text className={'text-xs text-orange-500'}>
|
||||
配送费:{displayQty}桶 x {deliveryFloor - 1}层 = ¥{getDeliveryFee().toFixed(2)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</Cell>
|
||||
</CellGroup>
|
||||
|
||||
<CellGroup>
|
||||
<Cell
|
||||
title={'配送时间'}
|
||||
@@ -1297,6 +1425,49 @@ const OrderConfirm = () => {
|
||||
</View>
|
||||
</Popup>
|
||||
|
||||
{/* 楼层选择弹窗 */}
|
||||
<Popup
|
||||
visible={floorPickerVisible}
|
||||
position="bottom"
|
||||
onClose={() => setFloorPickerVisible(false)}
|
||||
style={{height: '40vh'}}
|
||||
>
|
||||
<View className="floor-picker-popup">
|
||||
<View className="floor-picker-popup__header">
|
||||
<Text className="text-base font-medium">选择楼层</Text>
|
||||
<Text
|
||||
className="text-sm text-gray-500"
|
||||
onClick={() => setFloorPickerVisible(false)}
|
||||
>
|
||||
关闭
|
||||
</Text>
|
||||
</View>
|
||||
<View className="floor-picker-popup__content">
|
||||
<View className="floor-grid">
|
||||
{Array.from({length: 32}, (_, i) => i + 2).map(f => (
|
||||
<View
|
||||
key={f}
|
||||
className={`floor-grid-item ${deliveryFloor === f ? 'active' : ''}`}
|
||||
onClick={() => {
|
||||
setDeliveryFloor(f)
|
||||
setFloorPickerVisible(false)
|
||||
}}
|
||||
>
|
||||
<Text>{f}楼</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
{deliveryFloor > 1 && (
|
||||
<View className="floor-picker-popup__footer">
|
||||
<Text className={'text-sm text-gray-600'}>
|
||||
配送费:{displayQty}桶 x {deliveryFloor - 1}层 = <Text className={'text-red-500 font-bold'}>¥{(displayQty * (deliveryFloor - 1)).toFixed(2)}</Text>
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</Popup>
|
||||
|
||||
<Gap height={50}/>
|
||||
|
||||
<div className={'fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-10 border-t border-gray-200'}>
|
||||
@@ -1309,6 +1480,11 @@ const OrderConfirm = () => {
|
||||
</span>
|
||||
<span className={'text-sm text-gray-500'}>张</span>
|
||||
</View>
|
||||
{getDeliveryFee() > 0 && (
|
||||
<View className={'text-xs text-orange-500'}>
|
||||
配送费 ¥{getDeliveryFee().toFixed(2)}(到付)
|
||||
</View>
|
||||
)}
|
||||
</div>
|
||||
<div className={'buy-btn mx-4'}>
|
||||
{noUsableTickets && !isEditMode ? (
|
||||
@@ -1321,12 +1497,14 @@ const OrderConfirm = () => {
|
||||
size="large"
|
||||
loading={submitLoading || deliveryRangeChecking}
|
||||
disabled={
|
||||
deliveryRangeChecking ||
|
||||
deliveryRangeChecking ||
|
||||
!address?.id ||
|
||||
!addressHasCoords ||
|
||||
(deliveryRangeCheckedAddressId === address?.id && inDeliveryRange === false) ||
|
||||
(!isEditMode && availableTicketTotal <= 0) ||
|
||||
!canStartOrder
|
||||
!canStartOrder ||
|
||||
!deliveryMethod ||
|
||||
(deliveryMethod === 'stairs' && needCarryUpstairs === null)
|
||||
}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
@@ -1338,7 +1516,13 @@ const OrderConfirm = () => {
|
||||
? '地址缺少定位'
|
||||
: ((deliveryRangeCheckedAddressId === address?.id && inDeliveryRange === false)
|
||||
? '不在配送范围'
|
||||
: (submitLoading ? '提交中...' : (isEditMode ? '确定修改' : '立即提交'))
|
||||
: (!deliveryMethod
|
||||
? '请选配送方式'
|
||||
: (deliveryMethod === 'stairs' && needCarryUpstairs === null
|
||||
? '请选是否送上楼'
|
||||
: (submitLoading ? '提交中...' : (isEditMode ? '确定修改' : '立即提交'))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user