feat(rider): 添加配送员模块和订单图片保存功能

- 新增配送员首页界面,包含订单管理、工资明细、配送小区、仓库地址等功能入口
- 实现小程序码保存到相册功能,支持权限检查和错误处理
- 添加相册写入权限配置和图片下载临时路径处理
- 修复订单列表商品信息显示问题,优化支付流程
- 更新首页轮播图广告代码,调整用户中心网格布局
- 增加订单页面返回时的数据刷新机制,提升用户体验
This commit is contained in:
2026-01-31 02:52:28 +08:00
parent 7227ec6d84
commit f5c6d52b78
10 changed files with 531 additions and 104 deletions

View File

@@ -1,5 +1,5 @@
import {Avatar, Cell, Space, Empty, Tabs, Button, TabPane, Image, Dialog} from '@nutui/nutui-react-taro'
import {useEffect, useState, useCallback, CSSProperties} from "react";
import {useEffect, useState, useCallback, useRef, CSSProperties} from "react";
import {View, Text} from '@tarojs/components'
import Taro from '@tarojs/taro';
import {InfiniteLoading} from '@nutui/nutui-react-taro'
@@ -80,7 +80,8 @@ const tabs = [
// 扩展订单接口,包含商品信息
interface OrderWithGoods extends ShopOrder {
orderGoods?: ShopOrderGoods[];
// 避免与 ShopOrder.orderGoods (OrderGoods[]) 冲突:这里使用单独字段保存接口返回的商品明细
orderGoodsList?: ShopOrderGoods[];
}
interface OrderListProps {
@@ -92,8 +93,9 @@ interface OrderListProps {
function OrderList(props: OrderListProps) {
const [list, setList] = useState<OrderWithGoods[]>([])
const [page, setPage] = useState(1)
const pageRef = useRef(1)
const [hasMore, setHasMore] = useState(true)
const [payingOrderId, setPayingOrderId] = useState<number | null>(null)
// 根据传入的statusFilter设置初始tab索引
const getInitialTabIndex = () => {
if (props.searchParams?.statusFilter !== undefined) {
@@ -183,7 +185,7 @@ function OrderList(props: OrderListProps) {
const reload = useCallback(async (resetPage = false, targetPage?: number) => {
setLoading(true);
setError(null); // 清除之前的错误
const currentPage = resetPage ? 1 : (targetPage || page);
const currentPage = resetPage ? 1 : (targetPage || pageRef.current);
const statusParams = getOrderStatusParams(tapIndex);
// 合并搜索条件tab的statusFilter优先级更高
const searchConditions: any = {
@@ -205,7 +207,6 @@ function OrderList(props: OrderListProps) {
try {
const res = await pageShopOrder(searchConditions);
let newList: OrderWithGoods[];
if (res?.list && res?.list.length > 0) {
// 批量获取订单商品信息,限制并发数量
@@ -214,19 +215,19 @@ function OrderList(props: OrderListProps) {
for (let i = 0; i < res.list.length; i += batchSize) {
const batch = res.list.slice(i, i + batchSize);
const batchResults = await Promise.all(
const batchResults = await Promise.all(
batch.map(async (order) => {
try {
const orderGoods = await listShopOrderGoods({orderId: order.orderId});
return {
...order,
orderGoods: orderGoods || []
orderGoodsList: orderGoods || []
};
} catch (error) {
console.error('获取订单商品失败:', error);
return {
...order,
orderGoods: []
orderGoodsList: []
};
}
})
@@ -248,7 +249,7 @@ function OrderList(props: OrderListProps) {
setHasMore(false);
}
setPage(currentPage);
pageRef.current = currentPage;
setLoading(false);
} catch (error) {
console.error('加载订单失败:', error);
@@ -260,14 +261,14 @@ function OrderList(props: OrderListProps) {
icon: 'none'
});
}
}, [tapIndex, page, props.searchParams]); // 移除 list 依赖
}, [tapIndex, props.searchParams]); // 移除 list/page 依赖避免useEffect触发循环
const reloadMore = useCallback(async () => {
if (loading || !hasMore) return; // 防止重复加载
const nextPage = page + 1;
setPage(nextPage);
const nextPage = pageRef.current + 1;
pageRef.current = nextPage;
await reload(false, nextPage);
}, [loading, hasMore, page, reload]);
}, [loading, hasMore, reload]);
// 确认收货 - 显示确认对话框
const confirmReceive = (order: ShopOrder) => {
@@ -325,7 +326,7 @@ function OrderList(props: OrderListProps) {
});
// 更新本地状态
setDataSource(prev => prev.map(item =>
setList(prev => prev.map(item =>
item.orderId === order.orderId ? {...item, orderStatus: 4} : item
));
@@ -351,49 +352,23 @@ function OrderList(props: OrderListProps) {
};
// 再次购买 (已完成状态)
const buyAgain = (order: ShopOrder) => {
const buyAgain = (order: OrderWithGoods) => {
console.log('再次购买:', order);
goTo(`/shop/orderConfirm/index?goodsId=${order.orderGoods[0].goodsId}`)
const goodsId = order.orderGoodsList?.[0]?.goodsId
if (!goodsId) {
Taro.showToast({
title: '订单商品信息缺失',
icon: 'none'
});
return;
}
goTo(`/shop/orderConfirm/index?goodsId=${goodsId}`)
// Taro.showToast({
// title: '再次购买功能开发中',
// icon: 'none'
// });
};
// 评价商品 (已完成状态)
const evaluateGoods = (order: ShopOrder) => {
// 跳转到评价页面
Taro.navigateTo({
url: `/user/order/evaluate/index?orderId=${order.orderId}&orderNo=${order.orderNo}`
});
};
// 查看进度 (退款/售后状态)
const viewProgress = (order: ShopOrder) => {
// 根据订单状态确定售后类型
let afterSaleType = 'refund' // 默认退款
if (order.orderStatus === 4) {
afterSaleType = 'refund' // 退款申请中
} else if (order.orderStatus === 7) {
afterSaleType = 'return' // 退货申请中
}
// 跳转到售后进度页面
Taro.navigateTo({
url: `/user/order/progress/index?orderId=${order.orderId}&orderNo=${order.orderNo}&type=${afterSaleType}`
});
};
// 撤销申请 (退款/售后状态)
const cancelApplication = (order: ShopOrder) => {
console.log('撤销申请:', order);
Taro.showToast({
title: '撤销申请功能开发中',
icon: 'none'
});
};
// 取消订单
const cancelOrder = (order: ShopOrder) => {
setOrderToCancel(order);
@@ -437,7 +412,7 @@ function OrderList(props: OrderListProps) {
};
// 立即支付
const payOrder = async (order: ShopOrder) => {
const payOrder = async (order: OrderWithGoods) => {
try {
if (!order.orderId || !order.orderNo) {
Taro.showToast({
@@ -447,6 +422,11 @@ function OrderList(props: OrderListProps) {
return;
}
if (payingOrderId === order.orderId) {
return;
}
setPayingOrderId(order.orderId);
// 检查订单是否已过期
if (order.createTime && isPaymentExpired(order.createTime)) {
Taro.showToast({
@@ -475,11 +455,34 @@ function OrderList(props: OrderListProps) {
Taro.showLoading({title: '发起支付...'});
// 构建商品数据
const goodsItems = order.orderGoods?.map(goods => ({
goodsId: goods.goodsId,
quantity: goods.totalNum || 1
})) || [];
// 构建商品数据优先使用列表已加载的商品信息缺失时再补拉一次避免goodsItems为空导致后端拒绝/再次支付失败
let orderGoods = order.orderGoodsList || [];
if (!orderGoods.length) {
try {
orderGoods = (await listShopOrderGoods({orderId: order.orderId})) || [];
} catch (e) {
// 继续走下面的校验提示
console.error('补拉订单商品失败:', e);
}
}
const goodsItems = orderGoods
.filter(g => !!g.goodsId)
.map(goods => ({
goodsId: goods.goodsId as number,
quantity: goods.totalNum || 1,
// 若后端按SKU计算价格/库存补齐SKU/规格信息更安全
skuId: (goods as any).skuId,
specInfo: (goods as any).spec || (goods as any).specInfo
}));
if (!goodsItems.length) {
Taro.showToast({
title: '订单商品信息缺失,请稍后重试',
icon: 'none'
});
return;
}
// 对于已存在的订单,我们需要重新发起支付
// 构建支付请求数据,包含完整的商品信息
@@ -488,7 +491,13 @@ function OrderList(props: OrderListProps) {
orderNo: order.orderNo,
goodsItems: goodsItems,
addressId: order.addressId,
payType: PaymentType.WECHAT
payType: PaymentType.WECHAT,
// 尽量携带原订单信息,避免后端重新计算/校验不一致(如使用了优惠券/自提等)
couponId: order.couponId,
deliveryType: order.deliveryType,
selfTakeMerchantId: order.selfTakeMerchantId,
comments: order.comments,
title: order.title
};
console.log('重新支付数据:', paymentData);
@@ -506,13 +515,26 @@ function OrderList(props: OrderListProps) {
}
// 调用微信支付
await Taro.requestPayment({
timeStamp: result.timeStamp,
nonceStr: result.nonceStr,
package: result.package,
signType: (result.signType || 'MD5') as 'MD5' | 'HMAC-SHA256',
paySign: result.paySign,
});
try {
await Taro.requestPayment({
timeStamp: result.timeStamp,
nonceStr: result.nonceStr,
package: result.package,
signType: (result.signType || 'MD5') as 'MD5' | 'HMAC-SHA256',
paySign: result.paySign,
});
} catch (payError: any) {
const msg: string = payError?.errMsg || payError?.message || '';
if (msg.includes('cancel')) {
// 用户主动取消,不当作“失败”强提示
Taro.showToast({
title: '已取消支付',
icon: 'none'
});
return;
}
throw payError;
}
// 支付成功
Taro.showToast({
@@ -533,13 +555,14 @@ function OrderList(props: OrderListProps) {
console.error('支付失败:', error);
let errorMessage = '支付失败,请重试';
if (error.message) {
if (error.message.includes('cancel')) {
const rawMsg: string = error?.errMsg || error?.message || '';
if (rawMsg) {
if (rawMsg.includes('cancel')) {
errorMessage = '用户取消支付';
} else if (error.message.includes('余额不足')) {
} else if (rawMsg.includes('余额不足')) {
errorMessage = '账户余额不足';
} else {
errorMessage = error.message;
errorMessage = rawMsg;
}
}
@@ -549,13 +572,13 @@ function OrderList(props: OrderListProps) {
});
} finally {
Taro.hideLoading();
setPayingOrderId(null);
}
};
useEffect(() => {
void reload(true); // 首次加载tab切换时重置页码
}, [tapIndex]); // 只监听tapIndex变化避免reload依赖循环
void reload(true); // 首次加载tab切换或搜索条件变化时重置页码
}, [reload]);
// 监听外部statusFilter变化同步更新tab索引
useEffect(() => {
@@ -705,8 +728,8 @@ function OrderList(props: OrderListProps) {
{/* 商品信息 */}
<View className={'goods-info'}>
{item.orderGoods && item.orderGoods.length > 0 ? (
item.orderGoods.map((goods, goodsIndex) => (
{item.orderGoodsList && item.orderGoodsList.length > 0 ? (
item.orderGoodsList.map((goods, goodsIndex) => (
<View key={goodsIndex} className={'flex items-center mb-2'}>
<Image
src={goods.image || '/default-goods.png'}