forked from gxwebsoft/mp-10550
feat(ticket): 完善水票配送订单功能
- 优化导入路径,修复 PageParam 类型引用 - 新增 DeliverConfirmMode 类型定义,支持拍照完成和等待客户确认两种模式 - 实现配送确认的双模式功能,支持直接完成和等待确认流程 - 重构订单状态判断逻辑,完善配送流程状态管理 - 新增用户端确认收货功能,支持手动确认收货操作 - 优化订单列表展示,增加票号、取货点、门店电话等详细信息 - 添加地址复制和联系门店功能按钮 - 实现补传照片完成订单功能 - 更新订单流程状态显示,提供更准确的状态标识 - 添加配送确认模式切换的单选框界面 - 优化下单成功后的页面跳转逻辑 - 新增水票配送订单后端接口设计文档
This commit is contained in:
41
docs/水票配送订单-后端提示词.md
Normal file
41
docs/水票配送订单-后端提示词.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# 水票配送订单:后端提示词(可直接发给后端)
|
||||||
|
|
||||||
|
## 1) 订单查询(配送员端)
|
||||||
|
请在 `GET /glt/glt-ticket-order/page` 支持以下筛选,并保证权限隔离:
|
||||||
|
- `riderId`:只返回该配送员的订单(必要)
|
||||||
|
- `deliveryStatus`:10待配送、20配送中、30待客户确认、40已完成(必要)
|
||||||
|
- 排序:建议 `sendTime asc` + `createTime desc`(或给前端一个可控排序字段)
|
||||||
|
|
||||||
|
## 2) 配送流程字段(建议后端落库并回传)
|
||||||
|
订单表建议确保有以下字段(当前前端已按这些字段做流程判断/展示):
|
||||||
|
- `riderId/riderName/riderPhone`:配送员信息
|
||||||
|
- `deliveryStatus`:10/20/30/40
|
||||||
|
- `sendStartTime`:配送员点击“开始配送”的时间
|
||||||
|
- `sendEndTime`:配送员点击“确认送达”的时间
|
||||||
|
- `sendEndImg`:送达拍照留档图片 URL(可选/必填由后端策略决定)
|
||||||
|
- `receiveConfirmTime`:客户确认收货时间
|
||||||
|
- `receiveConfirmType`:10客户手动确认、20配送照片自动确认、30超时自动确认
|
||||||
|
|
||||||
|
## 3) 状态流转与校验(强烈建议在后端做)
|
||||||
|
请在更新订单时做状态机校验,避免前端绕过流程:
|
||||||
|
- `10 -> 20`:仅允许订单属于当前配送员,且未开始/未送达
|
||||||
|
- `20 -> 30`:配送员确认送达(可带 `sendEndImg`)
|
||||||
|
- `20/30 -> 40`:完成;来源可能是
|
||||||
|
- 客户手动确认(写 `receiveConfirmTime` + `receiveConfirmType=10`)
|
||||||
|
- 配送照片直接完成(写 `receiveConfirmTime` + `receiveConfirmType=20`,并要求 `sendEndImg`)
|
||||||
|
- 超时自动确认(写 `receiveConfirmTime` + `receiveConfirmType=30`,建议由定时任务执行)
|
||||||
|
|
||||||
|
## 4) 建议新增/明确的接口能力
|
||||||
|
为了避免并发抢单/越权更新,建议新增更语义化的接口(或在 update 内做等价校验):
|
||||||
|
- 接单(抢单/派单):`POST /glt/glt-ticket-order/{id}/accept`
|
||||||
|
- 后端原子校验:仅当 `riderId is null` 才能写入当前 rider 信息
|
||||||
|
- 开始配送:`POST /glt/glt-ticket-order/{id}/start`(写 `sendStartTime` + `deliveryStatus=20`)
|
||||||
|
- 确认送达:`POST /glt/glt-ticket-order/{id}/delivered`(写 `sendEndTime` + `deliveryStatus=30` + 可选 `sendEndImg`)
|
||||||
|
- 客户确认收货:`POST /glt/glt-ticket-order/{id}/confirm-receive`
|
||||||
|
- 校验:只能本人 `userId` 操作,且必须已送达
|
||||||
|
|
||||||
|
## 5) 为了“导航到收货地址/取货点”的字段补充(建议)
|
||||||
|
当前仅有 `address` 字符串,无法在小程序内 `openLocation` 精准导航;建议补充:
|
||||||
|
- 收货地址:`receiverName`、`receiverPhone`、`province/city/district/detail`、`latitude/longitude`
|
||||||
|
- 取货点(门店/仓库):`storeLatitude/storeLongitude` 或 `warehouseLatitude/warehouseLongitude`
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { PageParam } from '@/api/index';
|
import type { PageParam } from '@/api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 送水订单
|
* 送水订单
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
import { View, Text, Image } from '@tarojs/components';
|
import { View, Text, Image } from '@tarojs/components';
|
||||||
import { pageGltUserTicket } from '@/api/glt/gltUserTicket';
|
import { pageGltUserTicket } from '@/api/glt/gltUserTicket';
|
||||||
import type { GltUserTicket } from '@/api/glt/gltUserTicket/model';
|
import type { GltUserTicket } from '@/api/glt/gltUserTicket/model';
|
||||||
import { pageGltTicketOrder } from '@/api/glt/gltTicketOrder';
|
import { pageGltTicketOrder, updateGltTicketOrder } from '@/api/glt/gltTicketOrder';
|
||||||
import type { GltTicketOrder } from '@/api/glt/gltTicketOrder/model';
|
import type { GltTicketOrder } from '@/api/glt/gltTicketOrder/model';
|
||||||
import { BaseUrl } from '@/config/app';
|
import { BaseUrl } from '@/config/app';
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
@@ -37,7 +37,10 @@ const UserTicketList = () => {
|
|||||||
const [orderPage, setOrderPage] = useState(1);
|
const [orderPage, setOrderPage] = useState(1);
|
||||||
const [orderTotal, setOrderTotal] = useState(0);
|
const [orderTotal, setOrderTotal] = useState(0);
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<'ticket' | 'order'>('ticket');
|
const [activeTab, setActiveTab] = useState<'ticket' | 'order'>(() => {
|
||||||
|
const tab = Taro.getCurrentInstance().router?.params?.tab
|
||||||
|
return tab === 'order' ? 'order' : 'ticket'
|
||||||
|
});
|
||||||
|
|
||||||
const [qrVisible, setQrVisible] = useState(false);
|
const [qrVisible, setQrVisible] = useState(false);
|
||||||
const [qrTicket, setQrTicket] = useState<GltUserTicket | null>(null);
|
const [qrTicket, setQrTicket] = useState<GltUserTicket | null>(null);
|
||||||
@@ -253,13 +256,56 @@ const UserTicketList = () => {
|
|||||||
return d.isValid() ? d.format('YYYY年MM月DD日 HH:mm:ss') : v;
|
return d.isValid() ? d.format('YYYY年MM月DD日 HH:mm:ss') : v;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOrderStatusText = (status?: number) => {
|
const getTicketOrderStatusMeta = (order: GltTicketOrder) => {
|
||||||
// Backend field meaning may vary; page asks for "是否送达".
|
if (order.status === 1) return { text: '已冻结', type: 'warning' as const };
|
||||||
if (status === 1) return { text: '已送达', type: 'success' as const };
|
|
||||||
if (status === 0) return { text: '未送达', type: 'warning' as const };
|
const ds = order.deliveryStatus
|
||||||
return { text: status == null ? '-' : String(status), type: 'primary' as const };
|
if (ds === 40 || order.receiveConfirmTime) return { text: '已完成', type: 'success' as const };
|
||||||
|
if (ds === 30 || order.sendEndTime) return { text: '待确认收货', type: 'primary' as const };
|
||||||
|
if (ds === 20 || order.sendStartTime) return { text: '配送中', type: 'primary' as const };
|
||||||
|
if (ds === 10 || order.riderId) return { text: '待配送', type: 'warning' as const };
|
||||||
|
return { text: '待派单', type: 'primary' as const };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const canUserConfirmReceive = (order: GltTicketOrder) => {
|
||||||
|
if (!order?.id) return false
|
||||||
|
if (order.status === 1) return false
|
||||||
|
if (order.deliveryStatus === 40) return false
|
||||||
|
if (order.receiveConfirmTime) return false
|
||||||
|
// 必须是“已送达”后才能确认收货
|
||||||
|
return !!order.sendEndTime || order.deliveryStatus === 30
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUserConfirmReceive = async (order: GltTicketOrder) => {
|
||||||
|
if (!order?.id) return
|
||||||
|
if (!canUserConfirmReceive(order)) return
|
||||||
|
|
||||||
|
const modal = await Taro.showModal({
|
||||||
|
title: '确认收货',
|
||||||
|
content: '请确认已收到本次送水,确认后将无法撤销。',
|
||||||
|
confirmText: '确认收货'
|
||||||
|
})
|
||||||
|
if (!modal.confirm) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
Taro.showLoading({ title: '提交中...' })
|
||||||
|
const now = dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
await updateGltTicketOrder({
|
||||||
|
id: order.id,
|
||||||
|
deliveryStatus: 40,
|
||||||
|
receiveConfirmTime: now,
|
||||||
|
receiveConfirmType: 10
|
||||||
|
})
|
||||||
|
Taro.showToast({ title: '已确认收货', icon: 'success' })
|
||||||
|
await reloadOrders(true)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('确认收货失败:', e)
|
||||||
|
Taro.showToast({ title: '确认失败,请重试', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
Taro.hideLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useDidShow(() => {
|
useDidShow(() => {
|
||||||
if (activeTab === 'ticket') {
|
if (activeTab === 'ticket') {
|
||||||
reloadTickets(true).then();
|
reloadTickets(true).then();
|
||||||
@@ -436,10 +482,52 @@ const UserTicketList = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{(() => {
|
{(() => {
|
||||||
const meta = getOrderStatusText(item.status);
|
const meta = getTicketOrderStatusMeta(item);
|
||||||
return <Tag type={meta.type}>{meta.text}</Tag>;
|
return <Tag type={meta.type}>{meta.text}</Tag>;
|
||||||
})()}
|
})()}
|
||||||
</View>
|
</View>
|
||||||
|
<View className="mt-2 text-xs text-gray-500">
|
||||||
|
<Text>订单号:{item.id ?? '-'}</Text>
|
||||||
|
</View>
|
||||||
|
<View className="mt-1 text-xs text-gray-500">
|
||||||
|
<Text>收货地址:{item.address || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
{item.storeName ? (
|
||||||
|
<View className="mt-1 text-xs text-gray-500">
|
||||||
|
<Text>门店:{item.storeName}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
{item.sendStartTime ? (
|
||||||
|
<View className="mt-1 text-xs text-gray-500">
|
||||||
|
<Text>开始配送:{formatDateTime(item.sendStartTime)}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
{item.sendEndTime ? (
|
||||||
|
<View className="mt-1 text-xs text-gray-500">
|
||||||
|
<Text>送达时间:{formatDateTime(item.sendEndTime)}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
{item.receiveConfirmTime ? (
|
||||||
|
<View className="mt-1 text-xs text-gray-500">
|
||||||
|
<Text>确认收货:{formatDateTime(item.receiveConfirmTime)}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
{item.sendEndImg ? (
|
||||||
|
<View className="mt-3">
|
||||||
|
<Image src={item.sendEndImg} mode="aspectFill" style={{ width: '100%', height: '160px', borderRadius: '8px' }} />
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
{canUserConfirmReceive(item) ? (
|
||||||
|
<View className="mt-3 flex justify-end">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => handleUserConfirmReceive(item)}
|
||||||
|
>
|
||||||
|
确认收货
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
Space,
|
Space,
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
Image,
|
Image,
|
||||||
Empty,
|
Empty,
|
||||||
InfiniteLoading,
|
InfiniteLoading,
|
||||||
@@ -22,6 +24,8 @@ import { uploadFile } from '@/api/system/file'
|
|||||||
|
|
||||||
const PAGE_SIZE = 10
|
const PAGE_SIZE = 10
|
||||||
|
|
||||||
|
type DeliverConfirmMode = 'photoComplete' | 'waitCustomerConfirm'
|
||||||
|
|
||||||
export default function TicketOrdersPage() {
|
export default function TicketOrdersPage() {
|
||||||
const riderId = useMemo(() => {
|
const riderId = useMemo(() => {
|
||||||
const raw = Taro.getStorageSync('UserId')
|
const raw = Taro.getStorageSync('UserId')
|
||||||
@@ -42,6 +46,7 @@ export default function TicketOrdersPage() {
|
|||||||
const [deliverSubmitting, setDeliverSubmitting] = useState(false)
|
const [deliverSubmitting, setDeliverSubmitting] = useState(false)
|
||||||
const [deliverOrder, setDeliverOrder] = useState<GltTicketOrder | null>(null)
|
const [deliverOrder, setDeliverOrder] = useState<GltTicketOrder | null>(null)
|
||||||
const [deliverImg, setDeliverImg] = useState<string | undefined>(undefined)
|
const [deliverImg, setDeliverImg] = useState<string | undefined>(undefined)
|
||||||
|
const [deliverConfirmMode, setDeliverConfirmMode] = useState<DeliverConfirmMode>('photoComplete')
|
||||||
|
|
||||||
const riderTabs = useMemo(
|
const riderTabs = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@@ -95,7 +100,21 @@ export default function TicketOrdersPage() {
|
|||||||
if (!riderId || order.riderId !== riderId) return false
|
if (!riderId || order.riderId !== riderId) return false
|
||||||
if (order.receiveConfirmTime) return false
|
if (order.receiveConfirmTime) return false
|
||||||
if (order.deliveryStatus === 40) return false
|
if (order.deliveryStatus === 40) return false
|
||||||
return !order.sendEndTime
|
if (order.sendEndTime) return false
|
||||||
|
|
||||||
|
// 只允许在“配送中”阶段确认送达
|
||||||
|
if (typeof order.deliveryStatus === 'number') return order.deliveryStatus === 20
|
||||||
|
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(
|
||||||
@@ -173,9 +192,10 @@ export default function TicketOrdersPage() {
|
|||||||
await reload(false)
|
await reload(false)
|
||||||
}, [hasMore, loading, reload])
|
}, [hasMore, loading, reload])
|
||||||
|
|
||||||
const openDeliverDialog = (order: GltTicketOrder) => {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,22 +230,41 @@ export default function TicketOrdersPage() {
|
|||||||
const handleConfirmDelivered = async () => {
|
const handleConfirmDelivered = async () => {
|
||||||
if (!deliverOrder?.id) 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 {
|
||||||
|
const now = dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
// 送达时间:首次“确认送达”写入;补传照片时不要覆盖原送达时间
|
||||||
|
const deliveredAt = deliverOrder.sendEndTime || now
|
||||||
// 说明:
|
// 说明:
|
||||||
// - sendEndImg:送达照片留档(可选/必填由后端策略决定)
|
// - waitCustomerConfirm:只标记“已送达”,进入待客户确认(客户点击确认收货后完成)
|
||||||
// - sendEndTime:配送员确认送达时间
|
// - photoComplete:拍照留档后可直接完成(由后端策略决定是否允许)
|
||||||
// - deliveryStatus:建议后端设置为 30(待客户确认)
|
const payload: GltTicketOrder =
|
||||||
await updateGltTicketOrder({
|
deliverConfirmMode === 'photoComplete'
|
||||||
|
? {
|
||||||
|
id: deliverOrder.id,
|
||||||
|
deliveryStatus: 40,
|
||||||
|
sendEndTime: deliveredAt,
|
||||||
|
sendEndImg: deliverImg,
|
||||||
|
receiveConfirmTime: now,
|
||||||
|
receiveConfirmType: 20
|
||||||
|
}
|
||||||
|
: {
|
||||||
id: deliverOrder.id,
|
id: deliverOrder.id,
|
||||||
deliveryStatus: 30,
|
deliveryStatus: 30,
|
||||||
sendEndTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
sendEndTime: deliveredAt,
|
||||||
sendEndImg: deliverImg
|
sendEndImg: deliverImg
|
||||||
})
|
}
|
||||||
|
|
||||||
|
await updateGltTicketOrder(payload)
|
||||||
Taro.showToast({ title: '已确认送达', icon: 'success' })
|
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
|
||||||
await reload(true)
|
await reload(true)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -320,13 +359,19 @@ export default function TicketOrdersPage() {
|
|||||||
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 || o.comments || ''
|
const remark = o.buyerRemarks || o.comments || ''
|
||||||
|
const ticketNo = o.userTicketId || '-'
|
||||||
|
|
||||||
const flow1Done = !!o.riderId
|
const flow1Done = !!o.riderId
|
||||||
const flow2Done = !!o.sendStartTime || (typeof o.deliveryStatus === 'number' && o.deliveryStatus >= 20)
|
const flow2Done =
|
||||||
const flow3Done = !!o.sendEndTime
|
!!o.sendStartTime || (typeof o.deliveryStatus === 'number' && o.deliveryStatus >= 20)
|
||||||
|
const flow3Done =
|
||||||
|
!!o.sendEndTime || (typeof o.deliveryStatus === 'number' && o.deliveryStatus >= 30)
|
||||||
const flow4Done = !!o.receiveConfirmTime || o.deliveryStatus === 40
|
const flow4Done = !!o.receiveConfirmTime || o.deliveryStatus === 40
|
||||||
|
|
||||||
const phoneToCall = o.phone
|
const phoneToCall = o.phone
|
||||||
|
const storePhone = o.storePhone
|
||||||
|
const pickupName = o.warehouseName || o.storeName
|
||||||
|
const pickupAddr = o.warehouseAddress || o.storeAddress
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Cell key={String(o.id)} style={{ padding: '16px' }}>
|
<Cell key={String(o.id)} style={{ padding: '16px' }}>
|
||||||
@@ -337,6 +382,7 @@ export default function TicketOrdersPage() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className="text-gray-400 text-xs mt-1">下单时间:{timeText}</View>
|
<View className="text-gray-400 text-xs mt-1">下单时间:{timeText}</View>
|
||||||
|
<View className="text-gray-400 text-xs mt-1">票号:{ticketNo}</View>
|
||||||
|
|
||||||
<View className="mt-3 bg-white rounded-lg">
|
<View className="mt-3 bg-white rounded-lg">
|
||||||
<View className="text-sm text-gray-700">
|
<View className="text-sm text-gray-700">
|
||||||
@@ -349,6 +395,16 @@ export default function TicketOrdersPage() {
|
|||||||
{o.nickname || '-'} {o.phone ? `(${o.phone})` : ''}
|
{o.nickname || '-'} {o.phone ? `(${o.phone})` : ''}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
|
<Text className="text-gray-500">取货点:</Text>
|
||||||
|
<Text>{pickupName || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
{pickupAddr ? (
|
||||||
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
|
<Text className="text-gray-500">取货地址:</Text>
|
||||||
|
<Text>{pickupAddr}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
<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>{o.sendTime ? dayjs(o.sendTime).format('YYYY-MM-DD HH:mm') : '-'}</Text>
|
<Text>{o.sendTime ? dayjs(o.sendTime).format('YYYY-MM-DD HH:mm') : '-'}</Text>
|
||||||
@@ -359,6 +415,12 @@ export default function TicketOrdersPage() {
|
|||||||
<Text className="text-gray-500 ml-3">门店:</Text>
|
<Text className="text-gray-500 ml-3">门店:</Text>
|
||||||
<Text>{o.storeName || '-'}</Text>
|
<Text>{o.storeName || '-'}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
{o.storePhone ? (
|
||||||
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
|
<Text className="text-gray-500">门店电话:</Text>
|
||||||
|
<Text>{o.storePhone}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
{remark ? (
|
{remark ? (
|
||||||
<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>
|
||||||
@@ -402,7 +464,7 @@ export default function TicketOrdersPage() {
|
|||||||
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
||||||
<Text className={flow3Done ? 'text-purple-600 font-medium' : 'text-gray-400'}>3 送达留档</Text>
|
<Text className={flow3Done ? 'text-purple-600 font-medium' : 'text-gray-400'}>3 送达留档</Text>
|
||||||
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
||||||
<Text className={flow4Done ? 'text-green-600 font-medium' : 'text-gray-400'}>4 客户确认收货</Text>
|
<Text className={flow4Done ? 'text-green-600 font-medium' : 'text-gray-400'}>4 完成</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className="mt-3 flex justify-end">
|
<View className="mt-3 flex justify-end">
|
||||||
@@ -418,6 +480,29 @@ export default function TicketOrdersPage() {
|
|||||||
联系客户
|
联系客户
|
||||||
</Button>
|
</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) && (
|
{canStartDeliver(o) && (
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -435,12 +520,24 @@ export default function TicketOrdersPage() {
|
|||||||
type="primary"
|
type="primary"
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
openDeliverDialog(o)
|
openDeliverDialog(o, { mode: 'waitCustomerConfirm' })
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
确认送达
|
确认送达
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{canCompleteByPhoto(o) && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
openDeliverDialog(o, { mode: 'photoComplete' })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
补传照片完成
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -455,7 +552,13 @@ export default function TicketOrdersPage() {
|
|||||||
<Dialog
|
<Dialog
|
||||||
title="确认送达"
|
title="确认送达"
|
||||||
visible={deliverDialogVisible}
|
visible={deliverDialogVisible}
|
||||||
confirmText={deliverSubmitting ? '提交中...' : '确认送达'}
|
confirmText={
|
||||||
|
deliverSubmitting
|
||||||
|
? '提交中...'
|
||||||
|
: deliverConfirmMode === 'photoComplete'
|
||||||
|
? '拍照完成'
|
||||||
|
: '确认送达'
|
||||||
|
}
|
||||||
cancelText="取消"
|
cancelText="取消"
|
||||||
onConfirm={handleConfirmDelivered}
|
onConfirm={handleConfirmDelivered}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
@@ -463,10 +566,18 @@ export default function TicketOrdersPage() {
|
|||||||
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 ? '重新拍照/上传' : '拍照/上传'}
|
||||||
@@ -483,7 +594,7 @@ export default function TicketOrdersPage() {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<View className="mt-3 text-xs text-gray-500">
|
<View className="mt-3 text-xs text-gray-500">
|
||||||
送达后订单进入“待客户确认”;客户在用户端确认收货或超时自动确认(需后端支持)。
|
说明:如选择“客户确认收货”,订单进入“待客户确认”;客户在用户端确认收货或超时自动确认(需后端支持)。
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ const OrderConfirm = () => {
|
|||||||
Taro.showToast({ title: '下单成功', icon: 'success' })
|
Taro.showToast({ title: '下单成功', icon: 'success' })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// 跳转到“我的送水订单”
|
// 跳转到“我的送水订单”
|
||||||
Taro.redirectTo({ url: '/user/ticket/orders/index' })
|
Taro.redirectTo({ url: '/user/ticket/index?tab=order' })
|
||||||
}, 800)
|
}, 800)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error('水票下单失败:', e)
|
console.error('水票下单失败:', e)
|
||||||
|
|||||||
Reference in New Issue
Block a user