forked from gxwebsoft/mp-10550
feat(rider): 更新配送订单功能以支持新的送水订单系统
- 将 ShopOrder 替换为 GltTicketOrder 类型 - 更新 API 调用从 pageShopOrder/updateShopOrder 到 pageGltTicketOrder/updateGltTicketOrder - 重构配送状态管理逻辑,使用 deliveryStatus 字段替代原有状态判断 - 添加新的配送流程按钮:开始配送、补传照片完成等功能 - 实现配送确认模式选择(拍照完成或等待客户确认) - 集成 PullToRefresh 下拉刷新组件提升用户体验 - 添加 Loading 组件优化加载状态显示 - 重构订单列表展示界面适配新订单类型和字段结构 - 实现订单状态颜色标识和流程进度显示 - 添加地址复制和联系门店功能按钮
This commit is contained in:
@@ -1,13 +1,28 @@
|
|||||||
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro, { useDidShow } from '@tarojs/taro'
|
||||||
import { Tabs, TabPane, Cell, Space, Button, Dialog, Image, Empty, InfiniteLoading} from '@nutui/nutui-react-taro'
|
import {
|
||||||
import {View, Text} from '@tarojs/components'
|
Tabs,
|
||||||
|
TabPane,
|
||||||
|
Cell,
|
||||||
|
Space,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
Image,
|
||||||
|
Empty,
|
||||||
|
InfiniteLoading,
|
||||||
|
PullToRefresh,
|
||||||
|
Loading
|
||||||
|
} from '@nutui/nutui-react-taro'
|
||||||
|
import { View, Text } from '@tarojs/components'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import {pageShopOrder, updateShopOrder} from '@/api/shop/shopOrder'
|
import { pageGltTicketOrder, updateGltTicketOrder } from '@/api/glt/gltTicketOrder'
|
||||||
import type {ShopOrder, ShopOrderParam} from '@/api/shop/shopOrder/model'
|
import type { GltTicketOrder, GltTicketOrderParam } from '@/api/glt/gltTicketOrder/model'
|
||||||
import {uploadFile} from '@/api/system/file'
|
import { uploadFile } from '@/api/system/file'
|
||||||
|
|
||||||
export default function RiderOrders() {
|
export default function RiderOrders() {
|
||||||
|
const PAGE_SIZE = 10
|
||||||
|
|
||||||
const riderId = useMemo(() => {
|
const riderId = useMemo(() => {
|
||||||
const v = Number(Taro.getStorageSync('UserId'))
|
const v = Number(Taro.getStorageSync('UserId'))
|
||||||
@@ -15,110 +30,149 @@ export default function RiderOrders() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const pageRef = useRef(1)
|
const pageRef = useRef(1)
|
||||||
|
const listRef = useRef<GltTicketOrder[]>([])
|
||||||
const [tabIndex, setTabIndex] = useState(0)
|
const [tabIndex, setTabIndex] = useState(0)
|
||||||
const [list, setList] = useState<ShopOrder[]>([])
|
const [list, setList] = useState<GltTicketOrder[]>([])
|
||||||
const [hasMore, setHasMore] = useState(true)
|
const [hasMore, setHasMore] = useState(true)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
const [deliverDialogVisible, setDeliverDialogVisible] = useState(false)
|
const [deliverDialogVisible, setDeliverDialogVisible] = useState(false)
|
||||||
const [deliverSubmitting, setDeliverSubmitting] = useState(false)
|
const [deliverSubmitting, setDeliverSubmitting] = useState(false)
|
||||||
const [deliverOrder, setDeliverOrder] = useState<ShopOrder | null>(null)
|
const [deliverOrder, setDeliverOrder] = useState<GltTicketOrder | null>(null)
|
||||||
const [deliverImg, setDeliverImg] = useState<string | undefined>(undefined)
|
const [deliverImg, setDeliverImg] = useState<string | undefined>(undefined)
|
||||||
|
|
||||||
// 前端展示用:后台可配置实际自动确认收货时长
|
type DeliverConfirmMode = 'photoComplete' | 'waitCustomerConfirm'
|
||||||
const AUTO_CONFIRM_RECEIVE_HOURS_FALLBACK = 24
|
const [deliverConfirmMode, setDeliverConfirmMode] = useState<DeliverConfirmMode>('photoComplete')
|
||||||
|
|
||||||
const riderTabs = useMemo(
|
const riderTabs = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{index: 0, title: '全部', statusFilter: -1},
|
{ index: 0, title: '全部' },
|
||||||
{index: 1, title: '配送中', statusFilter: 3}, // 后端:deliveryStatus=20
|
{ index: 1, title: '待配送', deliveryStatus: 10 },
|
||||||
{index: 2, title: '待客户确认', statusFilter: 3}, // 同上,前端再按 sendEndTime 细分
|
{ index: 2, title: '配送中', deliveryStatus: 20 },
|
||||||
{index: 3, title: '已完成', statusFilter: 5}, // 后端:orderStatus=1
|
{ index: 3, title: '待确认', deliveryStatus: 30 },
|
||||||
|
{ index: 4, title: '已完成', deliveryStatus: 40 }
|
||||||
],
|
],
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
const isAbnormalOrder = (order: ShopOrder) => {
|
const getOrderStatusText = (order: GltTicketOrder) => {
|
||||||
const s = order.orderStatus
|
if (order.status === 1) return '已冻结'
|
||||||
return s === 2 || s === 3 || s === 4 || s === 5 || s === 6 || s === 7
|
|
||||||
|
const deliveryStatus = order.deliveryStatus
|
||||||
|
if (deliveryStatus === 40) return '已完成'
|
||||||
|
if (deliveryStatus === 30) return '待客户确认'
|
||||||
|
if (deliveryStatus === 20) return '配送中'
|
||||||
|
if (deliveryStatus === 10) return '待配送'
|
||||||
|
|
||||||
|
// 兼容:如果后端暂未下发 deliveryStatus,就用时间字段推断
|
||||||
|
if (order.receiveConfirmTime) return '已完成'
|
||||||
|
if (order.sendEndTime) return '待客户确认'
|
||||||
|
if (order.sendStartTime) return '配送中'
|
||||||
|
if (order.riderId) return '待配送'
|
||||||
|
return '待派单'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOrderStatusText = (order: ShopOrder) => {
|
const getOrderStatusColor = (order: GltTicketOrder) => {
|
||||||
if (order.orderStatus === 2) return '已取消'
|
const text = getOrderStatusText(order)
|
||||||
if (order.orderStatus === 3) return '取消中'
|
if (text === '已完成') return 'text-green-600'
|
||||||
if (order.orderStatus === 4) return '退款申请中'
|
if (text === '待客户确认') return 'text-purple-600'
|
||||||
if (order.orderStatus === 5) return '退款被拒绝'
|
if (text === '配送中') return 'text-blue-600'
|
||||||
if (order.orderStatus === 6) return '退款成功'
|
if (text === '待配送') return 'text-amber-600'
|
||||||
if (order.orderStatus === 7) return '客户申请退款'
|
if (text === '已冻结') return 'text-orange-600'
|
||||||
if (!order.payStatus) return '未付款'
|
return 'text-gray-500'
|
||||||
if (order.orderStatus === 1) return '已完成'
|
|
||||||
|
|
||||||
// 配送员页:用 sendEndTime 表示“已送达收货点”
|
|
||||||
if (order.deliveryStatus === 20) {
|
|
||||||
if (order.sendEndTime) return '待客户确认收货'
|
|
||||||
return '配送中'
|
|
||||||
}
|
|
||||||
if (order.deliveryStatus === 10) return '待发货'
|
|
||||||
if (order.deliveryStatus === 30) return '部分发货'
|
|
||||||
return '处理中'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOrderStatusColor = (order: ShopOrder) => {
|
const canStartDeliver = (order: GltTicketOrder) => {
|
||||||
if (isAbnormalOrder(order)) return 'text-orange-500'
|
if (!order.id) return false
|
||||||
if (order.orderStatus === 1) return 'text-green-600'
|
if (order.status === 1) return false
|
||||||
if (order.sendEndTime) return 'text-purple-600'
|
if (!riderId || order.riderId !== riderId) return false
|
||||||
return 'text-blue-600'
|
if (order.deliveryStatus && order.deliveryStatus !== 10) return false
|
||||||
|
return !order.sendStartTime && !order.sendEndTime
|
||||||
}
|
}
|
||||||
|
|
||||||
const canConfirmDelivered = (order: ShopOrder) => {
|
const canConfirmDelivered = (order: GltTicketOrder) => {
|
||||||
if (!order.payStatus) return false
|
if (!order.id) return false
|
||||||
if (order.orderStatus === 1) return false
|
if (order.status === 1) return false
|
||||||
if (isAbnormalOrder(order)) return false
|
if (!riderId || order.riderId !== riderId) return false
|
||||||
|
if (order.receiveConfirmTime) return false
|
||||||
|
if (order.deliveryStatus === 40) return false
|
||||||
|
if (order.sendEndTime) return false
|
||||||
|
|
||||||
// 只允许在“配送中”阶段确认送达
|
// 只允许在“配送中”阶段确认送达
|
||||||
if (order.deliveryStatus !== 20) return false
|
if (typeof order.deliveryStatus === 'number') return order.deliveryStatus === 20
|
||||||
return !order.sendEndTime
|
return !!order.sendStartTime
|
||||||
|
}
|
||||||
|
|
||||||
|
const canCompleteByPhoto = (order: GltTicketOrder) => {
|
||||||
|
if (!order.id) return false
|
||||||
|
if (order.status === 1) return false
|
||||||
|
if (!riderId || order.riderId !== riderId) return false
|
||||||
|
if (order.receiveConfirmTime) return false
|
||||||
|
if (order.deliveryStatus === 40) return false
|
||||||
|
// 已送达但未完成:允许补传照片并直接完成
|
||||||
|
return !!order.sendEndTime
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterByTab = useCallback(
|
const filterByTab = useCallback(
|
||||||
(orders: ShopOrder[]) => {
|
(orders: GltTicketOrder[]) => {
|
||||||
if (tabIndex === 1) {
|
if (tabIndex === 0) return orders
|
||||||
// 配送中:未确认送达
|
|
||||||
return orders.filter(o => o.deliveryStatus === 20 && !o.sendEndTime && !isAbnormalOrder(o) && o.orderStatus !== 1)
|
const current = riderTabs.find(t => t.index === tabIndex)
|
||||||
}
|
const status = current?.deliveryStatus
|
||||||
if (tabIndex === 2) {
|
if (!status) return orders
|
||||||
// 待客户确认:已确认送达
|
|
||||||
return orders.filter(o => o.deliveryStatus === 20 && !!o.sendEndTime && !isAbnormalOrder(o) && o.orderStatus !== 1)
|
// 如果后端已实现 deliveryStatus 筛选,这里基本不会再过滤;否则用兼容逻辑兜底。
|
||||||
}
|
return orders.filter(o => {
|
||||||
if (tabIndex === 3) {
|
const ds = o.deliveryStatus
|
||||||
return orders.filter(o => o.orderStatus === 1)
|
if (typeof ds === 'number') return ds === status
|
||||||
}
|
if (status === 10) return !!o.riderId && !o.sendStartTime && !o.sendEndTime
|
||||||
return orders
|
if (status === 20) return !!o.sendStartTime && !o.sendEndTime
|
||||||
|
if (status === 30) return !!o.sendEndTime && !o.receiveConfirmTime
|
||||||
|
if (status === 40) return !!o.receiveConfirmTime
|
||||||
|
return true
|
||||||
|
})
|
||||||
},
|
},
|
||||||
[tabIndex]
|
[riderTabs, tabIndex]
|
||||||
)
|
)
|
||||||
|
|
||||||
const reload = useCallback(
|
const reload = useCallback(
|
||||||
async (resetPage = false) => {
|
async (resetPage = false) => {
|
||||||
if (!riderId) return
|
if (!riderId) return
|
||||||
|
if (loading) return
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
|
|
||||||
const currentPage = resetPage ? 1 : pageRef.current
|
const currentPage = resetPage ? 1 : pageRef.current
|
||||||
const currentTab = riderTabs.find(t => t.index === tabIndex) || riderTabs[0]
|
const currentTab = riderTabs.find(t => t.index === tabIndex)
|
||||||
|
const params: GltTicketOrderParam = {
|
||||||
const params: ShopOrderParam = {
|
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
|
limit: PAGE_SIZE,
|
||||||
riderId,
|
riderId,
|
||||||
statusFilter: currentTab.statusFilter,
|
deliveryStatus: currentTab?.deliveryStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await pageShopOrder(params)
|
const res = await pageGltTicketOrder(params as any)
|
||||||
const incoming = (res?.list || []) as ShopOrder[]
|
const incomingAll = (res?.list || []) as GltTicketOrder[]
|
||||||
setList(prev => (resetPage ? incoming : prev.concat(incoming)))
|
|
||||||
setHasMore(incoming.length >= 10)
|
// 兼容:后端若暂未实现 riderId 过滤,前端兜底过滤掉非本人的订单
|
||||||
pageRef.current = currentPage
|
const incoming = incomingAll.filter(o => o?.deleted !== 1 && o?.riderId === riderId)
|
||||||
|
|
||||||
|
const prev = resetPage ? [] : listRef.current
|
||||||
|
const next = resetPage ? incoming : prev.concat(incoming)
|
||||||
|
listRef.current = next
|
||||||
|
setList(next)
|
||||||
|
|
||||||
|
const total = typeof res?.count === 'number' ? res.count : undefined
|
||||||
|
const filteredOut = incomingAll.length - incoming.length
|
||||||
|
if (typeof total === 'number' && filteredOut === 0) {
|
||||||
|
setHasMore(next.length < total)
|
||||||
|
} else {
|
||||||
|
setHasMore(incomingAll.length >= PAGE_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
pageRef.current = currentPage + 1
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载配送订单失败:', e)
|
console.error('加载配送订单失败:', e)
|
||||||
setError('加载失败,请重试')
|
setError('加载失败,请重试')
|
||||||
@@ -127,18 +181,18 @@ export default function RiderOrders() {
|
|||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[riderId, riderTabs, tabIndex]
|
[PAGE_SIZE, loading, riderId, riderTabs, tabIndex]
|
||||||
)
|
)
|
||||||
|
|
||||||
const reloadMore = useCallback(async () => {
|
const reloadMore = useCallback(async () => {
|
||||||
if (loading || !hasMore) return
|
if (loading || !hasMore) return
|
||||||
pageRef.current += 1
|
|
||||||
await reload(false)
|
await reload(false)
|
||||||
}, [hasMore, loading, reload])
|
}, [hasMore, loading, reload])
|
||||||
|
|
||||||
const openDeliverDialog = (order: ShopOrder) => {
|
const openDeliverDialog = (order: GltTicketOrder, opts?: { mode?: DeliverConfirmMode }) => {
|
||||||
setDeliverOrder(order)
|
setDeliverOrder(order)
|
||||||
setDeliverImg(order.sendEndImg)
|
setDeliverImg(order.sendEndImg)
|
||||||
|
setDeliverConfirmMode(opts?.mode || (order.sendEndImg ? 'photoComplete' : 'waitCustomerConfirm'))
|
||||||
setDeliverDialogVisible(true)
|
setDeliverDialogVisible(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,40 +202,100 @@ export default function RiderOrders() {
|
|||||||
setDeliverImg(file?.url)
|
setDeliverImg(file?.url)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('上传送达照片失败:', e)
|
console.error('上传送达照片失败:', e)
|
||||||
Taro.showToast({title: '上传失败,请重试', icon: 'none'})
|
Taro.showToast({ title: '上传失败,请重试', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStartDeliver = async (order: GltTicketOrder) => {
|
||||||
|
if (!order?.id) return
|
||||||
|
if (!canStartDeliver(order)) return
|
||||||
|
try {
|
||||||
|
await updateGltTicketOrder({
|
||||||
|
id: order.id,
|
||||||
|
deliveryStatus: 20,
|
||||||
|
sendStartTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
})
|
||||||
|
Taro.showToast({ title: '已开始配送', icon: 'success' })
|
||||||
|
pageRef.current = 1
|
||||||
|
listRef.current = []
|
||||||
|
setList([])
|
||||||
|
setHasMore(true)
|
||||||
|
await reload(true)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('开始配送失败:', e)
|
||||||
|
Taro.showToast({ title: '开始配送失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleConfirmDelivered = async () => {
|
const handleConfirmDelivered = async () => {
|
||||||
if (!deliverOrder?.orderId) return
|
if (!deliverOrder?.id) return
|
||||||
if (deliverSubmitting) return
|
if (deliverSubmitting) return
|
||||||
|
if (deliverConfirmMode === 'photoComplete' && !deliverImg) {
|
||||||
|
Taro.showToast({ title: '请先拍照/上传送达照片', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
setDeliverSubmitting(true)
|
setDeliverSubmitting(true)
|
||||||
try {
|
try {
|
||||||
await updateShopOrder({
|
const now = dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
orderId: deliverOrder.orderId,
|
// 送达时间:首次“确认送达”写入;补传照片时不要覆盖原送达时间
|
||||||
// 用于前端/后端识别“配送员已送达收货点”
|
const deliveredAt = deliverOrder.sendEndTime || now
|
||||||
sendEndTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
// - waitCustomerConfirm:只标记“已送达”,进入待客户确认
|
||||||
sendEndImg: deliverImg,
|
// - photoComplete:拍照留档后可直接完成(是否允许由后端策略决定)
|
||||||
})
|
const payload: GltTicketOrder =
|
||||||
Taro.showToast({title: '已确认送达', icon: 'success'})
|
deliverConfirmMode === 'photoComplete'
|
||||||
|
? {
|
||||||
|
id: deliverOrder.id,
|
||||||
|
deliveryStatus: 40,
|
||||||
|
sendEndTime: deliveredAt,
|
||||||
|
sendEndImg: deliverImg,
|
||||||
|
receiveConfirmTime: now,
|
||||||
|
receiveConfirmType: 20
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
id: deliverOrder.id,
|
||||||
|
deliveryStatus: 30,
|
||||||
|
sendEndTime: deliveredAt,
|
||||||
|
sendEndImg: deliverImg
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateGltTicketOrder(payload)
|
||||||
|
|
||||||
|
Taro.showToast({ title: '已确认送达', icon: 'success' })
|
||||||
setDeliverDialogVisible(false)
|
setDeliverDialogVisible(false)
|
||||||
setDeliverOrder(null)
|
setDeliverOrder(null)
|
||||||
setDeliverImg(undefined)
|
setDeliverImg(undefined)
|
||||||
|
setDeliverConfirmMode('photoComplete')
|
||||||
pageRef.current = 1
|
pageRef.current = 1
|
||||||
|
listRef.current = []
|
||||||
|
setList([])
|
||||||
|
setHasMore(true)
|
||||||
await reload(true)
|
await reload(true)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('确认送达失败:', e)
|
console.error('确认送达失败:', e)
|
||||||
Taro.showToast({title: '确认送达失败', icon: 'none'})
|
Taro.showToast({ title: '确认送达失败', icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
setDeliverSubmitting(false)
|
setDeliverSubmitting(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
}, [])
|
listRef.current = list
|
||||||
|
}, [list])
|
||||||
|
|
||||||
|
useDidShow(() => {
|
||||||
|
pageRef.current = 1
|
||||||
|
listRef.current = []
|
||||||
|
setList([])
|
||||||
|
setHasMore(true)
|
||||||
|
void reload(true)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
pageRef.current = 1
|
pageRef.current = 1
|
||||||
|
listRef.current = []
|
||||||
|
setList([])
|
||||||
|
setHasMore(true)
|
||||||
void reload(true)
|
void reload(true)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [tabIndex, riderId])
|
}, [tabIndex, riderId])
|
||||||
@@ -213,150 +327,245 @@ export default function RiderOrders() {
|
|||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<View style={{height: '84vh', width: '100%', padding: '0', overflowY: 'auto', overflowX: 'hidden'}} id="rider-order-scroll">
|
<PullToRefresh
|
||||||
{error ? (
|
onRefresh={async () => {
|
||||||
<View className="flex flex-col items-center justify-center h-64">
|
pageRef.current = 1
|
||||||
<Text className="text-gray-500 mb-4">{error}</Text>
|
listRef.current = []
|
||||||
<Button size="small" type="primary" onClick={() => reload(true)}>
|
setList([])
|
||||||
重新加载
|
setHasMore(true)
|
||||||
</Button>
|
await reload(true)
|
||||||
</View>
|
}}
|
||||||
) : (
|
headHeight={60}
|
||||||
<InfiniteLoading
|
>
|
||||||
target="rider-order-scroll"
|
<View
|
||||||
hasMore={hasMore}
|
style={{ height: '84vh', width: '100%', padding: '0', overflowY: 'auto', overflowX: 'hidden' }}
|
||||||
onLoadMore={reloadMore}
|
id="rider-order-scroll"
|
||||||
loadingText={<>加载中</>}
|
>
|
||||||
loadMoreText={
|
{error ? (
|
||||||
displayList.length === 0 ? (
|
<View className="flex flex-col items-center justify-center h-64">
|
||||||
<Empty style={{backgroundColor: 'transparent'}} description="暂无配送订单"/>
|
<Text className="text-gray-500 mb-4">{error}</Text>
|
||||||
) : (
|
<Button size="small" type="primary" onClick={() => reload(true)}>
|
||||||
<View className="h-24">没有更多了</View>
|
重新加载
|
||||||
)
|
</Button>
|
||||||
}
|
</View>
|
||||||
>
|
) : (
|
||||||
{displayList.map((o, idx) => {
|
<InfiniteLoading
|
||||||
const phoneToCall = o.phone || o.mobile
|
target="rider-order-scroll"
|
||||||
const flow1Done = !!o.riderId
|
hasMore={hasMore}
|
||||||
const flow2Done = o.deliveryStatus === 20 || o.deliveryStatus === 30
|
onLoadMore={reloadMore}
|
||||||
const flow3Done = !!o.sendEndTime
|
loadingText={
|
||||||
const flow4Done = o.orderStatus === 1
|
<View className="flex justify-center items-center py-4">
|
||||||
// 直接使用订单分页接口返回的 orderGoods
|
<Loading />
|
||||||
const goodsList = o.orderGoods || []
|
<View className="ml-2">加载中...</View>
|
||||||
const goodsNameList = goodsList
|
</View>
|
||||||
.map(g => g?.goodsName || (g as any)?.goodsTitle || (g as any)?.title || (g as any)?.name)
|
}
|
||||||
.filter(Boolean) as string[]
|
loadMoreText={
|
||||||
const goodsSummary = goodsNameList.length
|
displayList.length === 0 ? (
|
||||||
? `${goodsNameList.slice(0, 3).join('、')}${goodsList.length > 3 ? ` 等${goodsList.length}件` : ''}`
|
<Empty style={{ backgroundColor: 'transparent' }} description="暂无配送订单" />
|
||||||
: (o.title || '-')
|
) : (
|
||||||
|
<View className="h-24 text-center text-gray-500">没有更多了</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{displayList.map(o => {
|
||||||
|
const timeText = o.createTime ? dayjs(o.createTime).format('YYYY-MM-DD HH:mm') : '-'
|
||||||
|
const addr = o.address || (o.addressId ? `地址ID:${o.addressId}` : '-')
|
||||||
|
const remark = o.buyerRemarks || o.comments || ''
|
||||||
|
const qty = Number(o.totalNum || 0)
|
||||||
|
|
||||||
const autoConfirmAt = o.sendEndTime
|
const flow1Done = !!o.riderId
|
||||||
? dayjs(o.sendEndTime).add(AUTO_CONFIRM_RECEIVE_HOURS_FALLBACK, 'hour')
|
const flow2Done = !!o.sendStartTime || (typeof o.deliveryStatus === 'number' && o.deliveryStatus >= 20)
|
||||||
: null
|
const flow3Done = !!o.sendEndTime || (typeof o.deliveryStatus === 'number' && o.deliveryStatus >= 30)
|
||||||
const autoConfirmLeftMin = autoConfirmAt ? autoConfirmAt.diff(dayjs(), 'minute') : null
|
const flow4Done = !!o.receiveConfirmTime || o.deliveryStatus === 40
|
||||||
|
|
||||||
return (
|
const phoneToCall = o.phone
|
||||||
<Cell
|
const storePhone = o.storePhone
|
||||||
key={`${o.orderId || idx}`}
|
const pickupName = o.warehouseName || o.storeName
|
||||||
style={{padding: '16px'}}
|
const pickupAddr = o.warehouseAddress || o.storeAddress
|
||||||
onClick={() => o.orderId && Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${o.orderId}`})}
|
|
||||||
>
|
|
||||||
<View className="w-full">
|
|
||||||
<View className="flex justify-between items-center">
|
|
||||||
<Text className="text-gray-800 font-bold text-sm">{o.orderNo || `订单#${o.orderId}`}</Text>
|
|
||||||
<Text className={`${getOrderStatusColor(o)} text-sm font-medium`}>{getOrderStatusText(o)}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className="text-gray-400 text-xs mt-1">
|
return (
|
||||||
下单时间:{o.createTime ? dayjs(o.createTime).format('YYYY-MM-DD HH:mm') : '-'}
|
<Cell key={String(o.id)} style={{ padding: '16px' }}>
|
||||||
</View>
|
<View className="w-full">
|
||||||
|
<View className="flex justify-between items-center">
|
||||||
|
<Text className="text-gray-800 font-bold text-sm">
|
||||||
|
{o.id ? `送水订单#${o.id}` : '送水订单'}
|
||||||
|
</Text>
|
||||||
|
<Text className={`${getOrderStatusColor(o)} text-sm font-medium`}>{getOrderStatusText(o)}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View className="mt-3 bg-white rounded-lg">
|
<View className="text-gray-400 text-xs mt-1">下单时间:{timeText}</View>
|
||||||
<View className="text-sm text-gray-700">
|
|
||||||
<Text className="text-gray-500">收货点:</Text>
|
<View className="mt-3 bg-white rounded-lg">
|
||||||
<Text>{o.selfTakeMerchantName || o.address || '-'}</Text>
|
<View className="text-sm text-gray-700">
|
||||||
</View>
|
<Text className="text-gray-500">客户:</Text>
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
<Text>{o.nickname || '-'} {o.phone ? `(${o.phone})` : ''}</Text>
|
||||||
<Text className="text-gray-500">客户:</Text>
|
</View>
|
||||||
<Text>{o.realName || '-'} {o.phone ? `(${o.phone})` : ''}</Text>
|
|
||||||
</View>
|
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
|
||||||
<Text className="text-gray-500">金额:</Text>
|
|
||||||
<Text>¥{o.payPrice || o.totalPrice || '-'}</Text>
|
|
||||||
<Text className="text-gray-500 ml-3">数量:</Text>
|
|
||||||
<Text>{o.totalNum ?? '-'}</Text>
|
|
||||||
</View>
|
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
|
||||||
<Text className="text-gray-500">商品:</Text>
|
|
||||||
<Text>{goodsSummary}</Text>
|
|
||||||
</View>
|
|
||||||
{o.sendEndTime && (
|
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
<Text className="text-gray-500">送达时间:</Text>
|
<Text className="text-gray-500">收货地址:</Text>
|
||||||
<Text>{dayjs(o.sendEndTime).format('YYYY-MM-DD HH:mm')}</Text>
|
<Text>{addr}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
{!!remark && (
|
||||||
</View>
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
|
<Text className="text-gray-500">买家留言:</Text>
|
||||||
|
<Text>{remark}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 配送流程 */}
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
<View className="mt-3 bg-gray-50 rounded-lg p-2 text-xs">
|
<Text className="text-gray-500">数量:</Text>
|
||||||
<Text className="text-gray-600">流程:</Text>
|
<Text>{Number.isFinite(qty) ? qty : '-'}</Text>
|
||||||
<Text className={flow1Done ? 'text-green-600 font-medium' : 'text-gray-400'}>1 派单</Text>
|
<Text className="text-gray-500 ml-3">金额:</Text>
|
||||||
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
<Text>¥{o.price || '-'}</Text>
|
||||||
<Text className={flow2Done ? (flow3Done ? 'text-green-600 font-medium' : 'text-blue-600 font-medium') : 'text-gray-400'}>2 配送中</Text>
|
|
||||||
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
|
||||||
<Text className={flow3Done ? (flow4Done ? 'text-green-600 font-medium' : 'text-purple-600 font-medium') : 'text-gray-400'}>3 送达收货点</Text>
|
|
||||||
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
|
||||||
<Text className={flow4Done ? 'text-green-600 font-medium' : 'text-gray-400'}>4 客户确认收货</Text>
|
|
||||||
|
|
||||||
{o.sendEndTime && o.orderStatus !== 1 && autoConfirmAt && (
|
|
||||||
<View className="mt-1 text-gray-500">
|
|
||||||
若客户未确认,预计 {autoConfirmAt.format('YYYY-MM-DD HH:mm')} 自动确认收货(以后台配置为准)
|
|
||||||
{typeof autoConfirmLeftMin === 'number' && autoConfirmLeftMin > 0 ? `,约剩余 ${Math.ceil(autoConfirmLeftMin / 60)} 小时` : ''}
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className="mt-3 flex justify-end">
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
<Space>
|
<Text className="text-gray-500">配送时间:</Text>
|
||||||
{!!phoneToCall && (
|
<Text>{o.sendTime ? dayjs(o.sendTime).format('YYYY-MM-DD HH:mm') : '-'}</Text>
|
||||||
<Button
|
</View>
|
||||||
size="small"
|
|
||||||
onClick={(e) => {
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
e.stopPropagation()
|
<Text className="text-gray-500">取水点:</Text>
|
||||||
Taro.makePhoneCall({phoneNumber: phoneToCall})
|
<Text>{pickupName || '-'}</Text>
|
||||||
}}
|
</View>
|
||||||
>
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
联系客户
|
<Text className="text-gray-500">取水地址:</Text>
|
||||||
</Button>
|
<Text>{pickupAddr || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{!!o.sendStartTime && (
|
||||||
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
|
<Text className="text-gray-500">开始配送:</Text>
|
||||||
|
<Text>{dayjs(o.sendStartTime).format('YYYY-MM-DD HH:mm')}</Text>
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
{canConfirmDelivered(o) && (
|
{!!o.sendEndTime && (
|
||||||
<Button
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
size="small"
|
<Text className="text-gray-500">送达时间:</Text>
|
||||||
type="primary"
|
<Text>{dayjs(o.sendEndTime).format('YYYY-MM-DD HH:mm')}</Text>
|
||||||
onClick={(e) => {
|
</View>
|
||||||
e.stopPropagation()
|
|
||||||
openDeliverDialog(o)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
确认送达
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</Space>
|
{!!o.receiveConfirmTime && (
|
||||||
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
|
<Text className="text-gray-500">完成时间:</Text>
|
||||||
|
<Text>{dayjs(o.receiveConfirmTime).format('YYYY-MM-DD HH:mm')}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{o.sendEndImg ? (
|
||||||
|
<View className="text-sm text-gray-700 mt-2">
|
||||||
|
<Text className="text-gray-500">送达照片:</Text>
|
||||||
|
<View className="mt-2">
|
||||||
|
<Image src={o.sendEndImg} width="100%" height="120" />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 配送流程 */}
|
||||||
|
<View className="mt-3 bg-gray-50 rounded-lg p-2 text-xs">
|
||||||
|
<Text className="text-gray-600">流程:</Text>
|
||||||
|
<Text className={flow1Done ? 'text-green-600 font-medium' : 'text-gray-400'}>1 派单</Text>
|
||||||
|
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
||||||
|
<Text className={flow2Done ? 'text-blue-600 font-medium' : 'text-gray-400'}>2 配送中</Text>
|
||||||
|
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
||||||
|
<Text className={flow3Done ? 'text-purple-600 font-medium' : 'text-gray-400'}>3 送达留档</Text>
|
||||||
|
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
||||||
|
<Text className={flow4Done ? 'text-green-600 font-medium' : 'text-gray-400'}>4 完成</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="mt-3 flex justify-end">
|
||||||
|
<Space>
|
||||||
|
{!!phoneToCall && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
Taro.makePhoneCall({ phoneNumber: phoneToCall })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
联系客户
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!!addr && addr !== '-' && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
void Taro.setClipboardData({ data: addr })
|
||||||
|
Taro.showToast({ title: '地址已复制', icon: 'none' })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
复制地址
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!!storePhone && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
Taro.makePhoneCall({ phoneNumber: storePhone })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
联系门店
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{canStartDeliver(o) && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
void handleStartDeliver(o)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
开始配送
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{canConfirmDelivered(o) && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
openDeliverDialog(o, { mode: 'waitCustomerConfirm' })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
确认送达
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{canCompleteByPhoto(o) && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
openDeliverDialog(o, { mode: 'photoComplete' })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
补传照片完成
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</Cell>
|
||||||
</Cell>
|
)
|
||||||
)
|
})}
|
||||||
})}
|
</InfiniteLoading>
|
||||||
</InfiniteLoading>
|
)}
|
||||||
)}
|
</View>
|
||||||
</View>
|
</PullToRefresh>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
title="确认送达"
|
title="确认送达"
|
||||||
visible={deliverDialogVisible}
|
visible={deliverDialogVisible}
|
||||||
confirmText={deliverSubmitting ? '提交中...' : '确认送达'}
|
confirmText={
|
||||||
|
deliverSubmitting
|
||||||
|
? '提交中...'
|
||||||
|
: deliverConfirmMode === 'photoComplete'
|
||||||
|
? '拍照完成'
|
||||||
|
: '确认送达'
|
||||||
|
}
|
||||||
cancelText="取消"
|
cancelText="取消"
|
||||||
onConfirm={handleConfirmDelivered}
|
onConfirm={handleConfirmDelivered}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
@@ -364,13 +573,21 @@ export default function RiderOrders() {
|
|||||||
setDeliverDialogVisible(false)
|
setDeliverDialogVisible(false)
|
||||||
setDeliverOrder(null)
|
setDeliverOrder(null)
|
||||||
setDeliverImg(undefined)
|
setDeliverImg(undefined)
|
||||||
|
setDeliverConfirmMode('photoComplete')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View className="text-sm text-gray-700">
|
<View className="text-sm text-gray-700">
|
||||||
<View>到达收货点后,可选拍照留存,再点确认送达。</View>
|
<View>到达收货点后,可选择“拍照留档直接完成”或“等待客户确认收货”。</View>
|
||||||
|
|
||||||
|
<View className="mt-3">
|
||||||
|
<RadioGroup value={deliverConfirmMode} onChange={v => setDeliverConfirmMode(v as DeliverConfirmMode)}>
|
||||||
|
<Radio value="photoComplete">拍照留档(直接完成)</Radio>
|
||||||
|
<Radio value="waitCustomerConfirm">客户确认收货(可不拍照)</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</View>
|
||||||
<View className="mt-3">
|
<View className="mt-3">
|
||||||
<Button size="small" onClick={handleChooseDeliverImg}>
|
<Button size="small" onClick={handleChooseDeliverImg}>
|
||||||
{deliverImg ? '重新拍照/上传' : '拍照/上传(选填)'}
|
{deliverImg ? '重新拍照/上传' : '拍照/上传'}
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
{deliverImg && (
|
{deliverImg && (
|
||||||
@@ -383,6 +600,9 @@ export default function RiderOrders() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
<View className="mt-3 text-xs text-gray-500">
|
||||||
|
说明:如选择“客户确认收货”,订单进入“待客户确认”;客户在用户端确认收货或超时自动确认(需后端支持)。
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user