feat(ticket): 将送水订单功能重构为配送订单系统
- 修改页面标题从"送水订单"为"配送订单" - 扩展订单模型增加配送相关字段:配送开始时间、结束时间、送达照片、配送状态、客户确认时间等 - 新增配送员角色相关字段和筛选参数 - 实现完整的配送流程管理:待配送、配送中、待确认、已完成状态流转 - 添加配送订单标签页切换功能,支持按状态分类查看 - 集成配送操作界面,支持开始配送和确认送达功能 - 实现配送照片上传和展示功能 - 优化订单列表显示,增加配送流程进度展示 - 添加配送相关的业务逻辑验证和状态判断方法
This commit is contained in:
@@ -34,6 +34,18 @@ export interface GltTicketOrder {
|
|||||||
address?: string;
|
address?: string;
|
||||||
// 配送时间
|
// 配送时间
|
||||||
sendTime?: string;
|
sendTime?: string;
|
||||||
|
// 配送开始时间(配送员点击“开始配送”)
|
||||||
|
sendStartTime?: string;
|
||||||
|
// 配送结束时间(配送员确认送达)
|
||||||
|
sendEndTime?: string;
|
||||||
|
// 配送员送达拍照(选填/必填由后端策略决定)
|
||||||
|
sendEndImg?: string;
|
||||||
|
// 发货/配送状态(建议:10待配送 20配送中 30待客户确认 40已完成)
|
||||||
|
deliveryStatus?: number;
|
||||||
|
// 客户确认收货时间(客户点击确认收货)
|
||||||
|
receiveConfirmTime?: string;
|
||||||
|
// 客户确认方式(建议:10客户手动确认 20配送照片自动确认 30后台超时自动确认)
|
||||||
|
receiveConfirmType?: number;
|
||||||
// 买家留言
|
// 买家留言
|
||||||
buyerRemarks?: string;
|
buyerRemarks?: string;
|
||||||
// 用于统计
|
// 用于统计
|
||||||
@@ -71,4 +83,10 @@ export interface GltTicketOrderParam extends PageParam {
|
|||||||
id?: number;
|
id?: number;
|
||||||
keywords?: string;
|
keywords?: string;
|
||||||
userId?: number;
|
userId?: number;
|
||||||
|
// 配送员用户ID(用于配送员端查询)
|
||||||
|
riderId?: number;
|
||||||
|
// 发货/配送状态(建议与 GltTicketOrder.deliveryStatus 对齐)
|
||||||
|
deliveryStatus?: number;
|
||||||
|
// 兼容 ShopOrderParam 的筛选字段(如后端已实现可直接复用)
|
||||||
|
statusFilter?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '送水订单',
|
navigationBarTitleText: '配送订单',
|
||||||
navigationBarTextStyle: 'black',
|
navigationBarTextStyle: 'black',
|
||||||
navigationBarBackgroundColor: '#ffffff'
|
navigationBarBackgroundColor: '#ffffff'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,119 +1,492 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import Taro, { useDidShow } from '@tarojs/taro'
|
import Taro, { useDidShow } from '@tarojs/taro'
|
||||||
import { View, Text } from '@tarojs/components'
|
import { View, Text } from '@tarojs/components'
|
||||||
import { Cell, CellGroup, InfiniteLoading, PullToRefresh, Empty, Loading } from '@nutui/nutui-react-taro'
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabPane,
|
||||||
|
Cell,
|
||||||
|
Space,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
Image,
|
||||||
|
Empty,
|
||||||
|
InfiniteLoading,
|
||||||
|
PullToRefresh,
|
||||||
|
Loading
|
||||||
|
} from '@nutui/nutui-react-taro'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import { pageGltTicketOrder } from '@/api/glt/gltTicketOrder'
|
import { pageGltTicketOrder, updateGltTicketOrder } from '@/api/glt/gltTicketOrder'
|
||||||
import type { GltTicketOrder } from '@/api/glt/gltTicketOrder/model'
|
import type { GltTicketOrder, GltTicketOrderParam } from '@/api/glt/gltTicketOrder/model'
|
||||||
|
import { uploadFile } from '@/api/system/file'
|
||||||
|
|
||||||
const PAGE_SIZE = 10
|
const PAGE_SIZE = 10
|
||||||
|
|
||||||
export default function TicketOrdersPage() {
|
export default function TicketOrdersPage() {
|
||||||
const [list, setList] = useState<GltTicketOrder[]>([])
|
const riderId = useMemo(() => {
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
const [hasMore, setHasMore] = useState(true)
|
|
||||||
const [page, setPage] = useState(1)
|
|
||||||
|
|
||||||
const userId = (() => {
|
|
||||||
const raw = Taro.getStorageSync('UserId')
|
const raw = Taro.getStorageSync('UserId')
|
||||||
const id = Number(raw)
|
const id = Number(raw)
|
||||||
return Number.isFinite(id) && id > 0 ? id : undefined
|
return Number.isFinite(id) && id > 0 ? id : undefined
|
||||||
})()
|
}, [])
|
||||||
|
|
||||||
const reload = async (isRefresh = true) => {
|
const pageRef = useRef(1)
|
||||||
if (loading) return
|
const listRef = useRef<GltTicketOrder[]>([])
|
||||||
if (!userId) {
|
|
||||||
setList([])
|
|
||||||
setHasMore(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true)
|
const [tabIndex, setTabIndex] = useState(0)
|
||||||
try {
|
const [list, setList] = useState<GltTicketOrder[]>([])
|
||||||
const currentPage = isRefresh ? 1 : page
|
const [hasMore, setHasMore] = useState(true)
|
||||||
const res = await pageGltTicketOrder({
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const [deliverDialogVisible, setDeliverDialogVisible] = useState(false)
|
||||||
|
const [deliverSubmitting, setDeliverSubmitting] = useState(false)
|
||||||
|
const [deliverOrder, setDeliverOrder] = useState<GltTicketOrder | null>(null)
|
||||||
|
const [deliverImg, setDeliverImg] = useState<string | undefined>(undefined)
|
||||||
|
|
||||||
|
const riderTabs = useMemo(
|
||||||
|
() => [
|
||||||
|
{ index: 0, title: '全部' },
|
||||||
|
{ index: 1, title: '待配送', deliveryStatus: 10 },
|
||||||
|
{ index: 2, title: '配送中', deliveryStatus: 20 },
|
||||||
|
{ index: 3, title: '待确认', deliveryStatus: 30 },
|
||||||
|
{ index: 4, title: '已完成', deliveryStatus: 40 }
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const getOrderStatusText = (order: GltTicketOrder) => {
|
||||||
|
if (order.status === 1) return '已冻结'
|
||||||
|
|
||||||
|
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 getOrderStatusColor = (order: GltTicketOrder) => {
|
||||||
|
const text = getOrderStatusText(order)
|
||||||
|
if (text === '已完成') return 'text-green-600'
|
||||||
|
if (text === '待客户确认') return 'text-purple-600'
|
||||||
|
if (text === '配送中') return 'text-blue-600'
|
||||||
|
if (text === '待配送') return 'text-amber-600'
|
||||||
|
if (text === '已冻结') return 'text-orange-600'
|
||||||
|
return 'text-gray-500'
|
||||||
|
}
|
||||||
|
|
||||||
|
const canStartDeliver = (order: GltTicketOrder) => {
|
||||||
|
if (!order.id) return false
|
||||||
|
if (order.status === 1) return false
|
||||||
|
if (!riderId || order.riderId !== riderId) return false
|
||||||
|
if (order.deliveryStatus && order.deliveryStatus !== 10) return false
|
||||||
|
return !order.sendStartTime && !order.sendEndTime
|
||||||
|
}
|
||||||
|
|
||||||
|
const canConfirmDelivered = (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(
|
||||||
|
(orders: GltTicketOrder[]) => {
|
||||||
|
if (tabIndex === 0) return orders
|
||||||
|
|
||||||
|
const current = riderTabs.find(t => t.index === tabIndex)
|
||||||
|
const status = current?.deliveryStatus
|
||||||
|
if (!status) return orders
|
||||||
|
|
||||||
|
// 如果后端已实现 deliveryStatus 筛选,这里基本不会再过滤;否则用兼容逻辑兜底。
|
||||||
|
return orders.filter(o => {
|
||||||
|
const ds = o.deliveryStatus
|
||||||
|
if (typeof ds === 'number') return ds === status
|
||||||
|
if (status === 10) return !!o.riderId && !o.sendStartTime && !o.sendEndTime
|
||||||
|
if (status === 20) return !!o.sendStartTime && !o.sendEndTime
|
||||||
|
if (status === 30) return !!o.sendEndTime && !o.receiveConfirmTime
|
||||||
|
if (status === 40) return !!o.receiveConfirmTime
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[riderTabs, tabIndex]
|
||||||
|
)
|
||||||
|
|
||||||
|
const reload = useCallback(
|
||||||
|
async (resetPage = false) => {
|
||||||
|
if (!riderId) return
|
||||||
|
if (loading) return
|
||||||
|
setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
const currentPage = resetPage ? 1 : pageRef.current
|
||||||
|
const currentTab = riderTabs.find(t => t.index === tabIndex)
|
||||||
|
|
||||||
|
const params: GltTicketOrderParam = {
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
limit: PAGE_SIZE,
|
limit: PAGE_SIZE,
|
||||||
userId
|
riderId,
|
||||||
} as any)
|
deliveryStatus: currentTab?.deliveryStatus
|
||||||
|
}
|
||||||
|
|
||||||
const resList = res?.list || []
|
try {
|
||||||
const next = isRefresh ? resList : [...list, ...resList]
|
const res = await pageGltTicketOrder(params as any)
|
||||||
setList(next)
|
const incomingAll = (res?.list || []) as GltTicketOrder[]
|
||||||
|
// 兼容:后端若暂未实现 riderId 过滤,前端兜底过滤掉非本人的订单
|
||||||
|
const incoming = incomingAll.filter(o => o?.deleted !== 1 && o?.riderId === riderId)
|
||||||
|
|
||||||
const total = typeof res?.count === 'number' ? res.count : next.length
|
const prev = resetPage ? [] : listRef.current
|
||||||
setHasMore(next.length < total)
|
const next = resetPage ? incoming : prev.concat(incoming)
|
||||||
setPage(currentPage + 1)
|
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) {
|
||||||
|
console.error('加载配送订单失败:', e)
|
||||||
|
setError('加载失败,请重试')
|
||||||
|
setHasMore(false)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[loading, riderId, riderTabs, tabIndex]
|
||||||
|
)
|
||||||
|
|
||||||
|
const reloadMore = useCallback(async () => {
|
||||||
|
if (loading || !hasMore) return
|
||||||
|
await reload(false)
|
||||||
|
}, [hasMore, loading, reload])
|
||||||
|
|
||||||
|
const openDeliverDialog = (order: GltTicketOrder) => {
|
||||||
|
setDeliverOrder(order)
|
||||||
|
setDeliverImg(order.sendEndImg)
|
||||||
|
setDeliverDialogVisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChooseDeliverImg = async () => {
|
||||||
|
try {
|
||||||
|
const file = await uploadFile()
|
||||||
|
setDeliverImg(file?.url)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('获取送水订单失败:', e)
|
console.error('上传送达照片失败:', e)
|
||||||
Taro.showToast({ title: '获取送水订单失败', icon: 'none' })
|
Taro.showToast({ title: '上传失败,请重试', icon: 'none' })
|
||||||
setHasMore(false)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
await reload(true)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('开始配送失败:', e)
|
||||||
|
Taro.showToast({ title: '开始配送失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirmDelivered = async () => {
|
||||||
|
if (!deliverOrder?.id) return
|
||||||
|
if (deliverSubmitting) return
|
||||||
|
setDeliverSubmitting(true)
|
||||||
|
try {
|
||||||
|
// 说明:
|
||||||
|
// - sendEndImg:送达照片留档(可选/必填由后端策略决定)
|
||||||
|
// - sendEndTime:配送员确认送达时间
|
||||||
|
// - deliveryStatus:建议后端设置为 30(待客户确认)
|
||||||
|
await updateGltTicketOrder({
|
||||||
|
id: deliverOrder.id,
|
||||||
|
deliveryStatus: 30,
|
||||||
|
sendEndTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
sendEndImg: deliverImg
|
||||||
|
})
|
||||||
|
Taro.showToast({ title: '已确认送达', icon: 'success' })
|
||||||
|
setDeliverDialogVisible(false)
|
||||||
|
setDeliverOrder(null)
|
||||||
|
setDeliverImg(undefined)
|
||||||
|
pageRef.current = 1
|
||||||
|
await reload(true)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('确认送达失败:', e)
|
||||||
|
Taro.showToast({ title: '确认送达失败', icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setDeliverSubmitting(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
}, [])
|
listRef.current = list
|
||||||
|
}, [list])
|
||||||
|
|
||||||
useDidShow(() => {
|
useDidShow(() => {
|
||||||
setPage(1)
|
pageRef.current = 1
|
||||||
|
listRef.current = []
|
||||||
|
setList([])
|
||||||
setHasMore(true)
|
setHasMore(true)
|
||||||
reload(true)
|
void reload(true)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
pageRef.current = 1
|
||||||
|
listRef.current = []
|
||||||
|
setList([])
|
||||||
|
setHasMore(true)
|
||||||
|
void reload(true)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [tabIndex, riderId])
|
||||||
|
|
||||||
|
if (!riderId) {
|
||||||
|
return (
|
||||||
|
<View className="bg-gray-50 min-h-screen p-4">
|
||||||
|
<Text>请先登录</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayList = filterByTab(list)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="bg-gray-50 min-h-screen">
|
<View className="bg-gray-50 min-h-screen">
|
||||||
|
<Tabs value={tabIndex} onChange={paneKey => setTabIndex(Number(paneKey))} align="left">
|
||||||
|
{riderTabs.map(t => (
|
||||||
|
<TabPane key={t.index} title={loading && tabIndex === t.index ? `${t.title}...` : t.title} />
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
<View className="px-3">
|
<View className="px-3 pb-4">
|
||||||
<PullToRefresh onRefresh={() => reload(true)}>
|
<PullToRefresh
|
||||||
{list.length === 0 && !loading ? (
|
onRefresh={async () => {
|
||||||
|
pageRef.current = 1
|
||||||
|
listRef.current = []
|
||||||
|
setList([])
|
||||||
|
setHasMore(true)
|
||||||
|
await reload(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{error ? (
|
||||||
<View className="bg-white rounded-lg p-6">
|
<View className="bg-white rounded-lg p-6">
|
||||||
<Empty description="暂无送水订单" />
|
<View className="flex flex-col items-center justify-center">
|
||||||
|
<Text className="text-gray-500 mb-3">{error}</Text>
|
||||||
|
<Button size="small" type="primary" onClick={() => reload(true)}>
|
||||||
|
重新加载
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<CellGroup>
|
<InfiniteLoading
|
||||||
{list.map((o) => {
|
hasMore={hasMore}
|
||||||
|
onLoadMore={reloadMore}
|
||||||
|
loadingText={
|
||||||
|
<View className="flex justify-center items-center py-4">
|
||||||
|
<Loading />
|
||||||
|
<View className="ml-2">加载中...</View>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
loadMoreText={
|
||||||
|
displayList.length === 0 ? (
|
||||||
|
<View className="bg-white rounded-lg p-6">
|
||||||
|
<Empty description="暂无配送订单" />
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View className="text-center py-4 text-gray-500">没有更多了</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{displayList.map(o => {
|
||||||
const qty = Number(o.totalNum || 0)
|
const qty = Number(o.totalNum || 0)
|
||||||
const timeText = o.createTime ? dayjs(o.createTime).format('YYYY-MM-DD HH:mm') : ''
|
const timeText = o.createTime ? dayjs(o.createTime).format('YYYY-MM-DD HH:mm') : '-'
|
||||||
const addr = o.address || (o.addressId ? `地址ID:${o.addressId}` : '')
|
const addr = o.address || (o.addressId ? `地址ID:${o.addressId}` : '-')
|
||||||
const remark = o.buyerRemarks || ''
|
const remark = o.buyerRemarks || o.comments || ''
|
||||||
|
|
||||||
|
const flow1Done = !!o.riderId
|
||||||
|
const flow2Done = !!o.sendStartTime || (typeof o.deliveryStatus === 'number' && o.deliveryStatus >= 20)
|
||||||
|
const flow3Done = !!o.sendEndTime
|
||||||
|
const flow4Done = !!o.receiveConfirmTime || o.deliveryStatus === 40
|
||||||
|
|
||||||
|
const phoneToCall = o.phone
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Cell
|
<Cell key={String(o.id)} style={{ padding: '16px' }}>
|
||||||
key={o.id}
|
<View className="w-full">
|
||||||
title={
|
<View className="flex justify-between items-center">
|
||||||
<View className="flex flex-col">
|
<Text className="text-gray-800 font-bold text-sm">{`订单#${o.id}`}</Text>
|
||||||
<Text className="text-sm text-gray-900">送水 {qty || '-'} 桶</Text>
|
<Text className={`${getOrderStatusColor(o)} text-sm font-medium`}>{getOrderStatusText(o)}</Text>
|
||||||
{addr ? <Text className="text-xs text-gray-500">{addr}</Text> : null}
|
|
||||||
{remark ? <Text className="text-xs text-gray-500">备注:{remark}</Text> : null}
|
|
||||||
</View>
|
</View>
|
||||||
}
|
|
||||||
extra={<Text className="text-xs text-gray-500">{timeText}</Text>}
|
<View className="text-gray-400 text-xs mt-1">下单时间:{timeText}</View>
|
||||||
/>
|
|
||||||
|
<View className="mt-3 bg-white rounded-lg">
|
||||||
|
<View className="text-sm text-gray-700">
|
||||||
|
<Text className="text-gray-500">收货地址:</Text>
|
||||||
|
<Text>{addr}</Text>
|
||||||
|
</View>
|
||||||
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
|
<Text className="text-gray-500">客户:</Text>
|
||||||
|
<Text>
|
||||||
|
{o.nickname || '-'} {o.phone ? `(${o.phone})` : ''}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
|
<Text className="text-gray-500">预约配送:</Text>
|
||||||
|
<Text>{o.sendTime ? dayjs(o.sendTime).format('YYYY-MM-DD HH:mm') : '-'}</Text>
|
||||||
|
</View>
|
||||||
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
|
<Text className="text-gray-500">数量:</Text>
|
||||||
|
<Text>{qty || '-'}</Text>
|
||||||
|
<Text className="text-gray-500 ml-3">门店:</Text>
|
||||||
|
<Text>{o.storeName || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
{remark ? (
|
||||||
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
|
<Text className="text-gray-500">备注:</Text>
|
||||||
|
<Text>{remark}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
{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>
|
||||||
|
) : null}
|
||||||
|
{o.sendEndTime ? (
|
||||||
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
|
<Text className="text-gray-500">送达时间:</Text>
|
||||||
|
<Text>{dayjs(o.sendEndTime).format('YYYY-MM-DD HH:mm')}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
{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>
|
||||||
|
) : null}
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
|
{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)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
确认送达
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Cell>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</CellGroup>
|
</InfiniteLoading>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<InfiniteLoading
|
|
||||||
hasMore={hasMore}
|
|
||||||
onLoadMore={() => reload(false)}
|
|
||||||
loadingText={
|
|
||||||
<View className="flex justify-center items-center py-4">
|
|
||||||
<Loading />
|
|
||||||
<View className="ml-2">加载中...</View>
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
loadMoreText={
|
|
||||||
<View className="text-center py-4 text-gray-500">
|
|
||||||
{list.length === 0 ? '暂无数据' : '没有更多了'}
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</PullToRefresh>
|
</PullToRefresh>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
title="确认送达"
|
||||||
|
visible={deliverDialogVisible}
|
||||||
|
confirmText={deliverSubmitting ? '提交中...' : '确认送达'}
|
||||||
|
cancelText="取消"
|
||||||
|
onConfirm={handleConfirmDelivered}
|
||||||
|
onCancel={() => {
|
||||||
|
if (deliverSubmitting) return
|
||||||
|
setDeliverDialogVisible(false)
|
||||||
|
setDeliverOrder(null)
|
||||||
|
setDeliverImg(undefined)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View className="text-sm text-gray-700">
|
||||||
|
<View>到达收货点后,可拍照留档(推荐/可设为必填),再点确认送达。</View>
|
||||||
|
<View className="mt-3">
|
||||||
|
<Button size="small" onClick={handleChooseDeliverImg}>
|
||||||
|
{deliverImg ? '重新拍照/上传' : '拍照/上传'}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
{deliverImg && (
|
||||||
|
<View className="mt-3">
|
||||||
|
<Image src={deliverImg} width="100%" height="120" />
|
||||||
|
<View className="mt-2 flex justify-end">
|
||||||
|
<Button size="small" onClick={() => setDeliverImg(undefined)}>
|
||||||
|
移除照片
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<View className="mt-3 text-xs text-gray-500">
|
||||||
|
送达后订单进入“待客户确认”;客户在用户端确认收货或超时自动确认(需后端支持)。
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Dialog>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,13 +277,13 @@ const OrderConfirm = () => {
|
|||||||
// 设置商品信息
|
// 设置商品信息
|
||||||
if (goodsRes) {
|
if (goodsRes) {
|
||||||
setGoods(goodsRes)
|
setGoods(goodsRes)
|
||||||
|
hasInitialLoadedRef.current = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置默认收货地址
|
// 设置默认收货地址
|
||||||
if (addressRes && addressRes.length > 0) {
|
if (addressRes && addressRes.length > 0) {
|
||||||
setAddress(addressRes[0])
|
setAddress(addressRes[0])
|
||||||
}
|
}
|
||||||
hasInitialLoadedRef.current = true
|
|
||||||
// Tickets are non-blocking for first paint; load in background.
|
// Tickets are non-blocking for first paint; load in background.
|
||||||
loadUserTickets()
|
loadUserTickets()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user