feat(pages): 添加管理页面功能和配置
- 创建 .editorconfig 文件统一代码风格配置 - 配置 .eslintrc 使用 taro/react 规则集 - 完善 .gitignore 忽略编译产物和敏感文件 - 添加 admin/article/add 页面实现文章管理功能 - 添加 dealer/apply/add 页面实现经销商申请功能 - 添加 dealer/bank/add 页面实现银行卡管理功能 - 添加 dealer/customer/add 页面实现客户管理功能 - 添加 user/address/add 页面实现用户地址管理功能 - 添加 user/chat/message/add 页面实现消息功能 - 添加 user/gift/add 页面实现礼品管理功能 - 配置各页面导航栏标题和样式 - 实现表单验证和数据提交功能 - 集成图片上传和头像选择功能 - 添加日期选择和数据校验逻辑 - 实现编辑和新增模式切换 - 集成用户权限和角色管理功能
This commit is contained in:
533
src/user/order/components/OrderList.tsx
Normal file
533
src/user/order/components/OrderList.tsx
Normal file
@@ -0,0 +1,533 @@
|
||||
import {Avatar, Cell, Space, Empty, Tabs, Button, TabPane, Image} from '@nutui/nutui-react-taro'
|
||||
import {useEffect, useState, CSSProperties} from "react";
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro';
|
||||
import {InfiniteLoading} from '@nutui/nutui-react-taro'
|
||||
import dayjs from "dayjs";
|
||||
import {pageShopOrder, updateShopOrder} from "@/api/shop/shopOrder";
|
||||
import {ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model";
|
||||
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
||||
import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model";
|
||||
import {copyText} from "@/utils/common";
|
||||
import PaymentCountdown from "@/components/PaymentCountdown";
|
||||
|
||||
// 判断订单是否支付已过期
|
||||
const isPaymentExpired = (createTime: string, timeoutHours: number = 24): boolean => {
|
||||
if (!createTime) return false;
|
||||
const createTimeObj = dayjs(createTime);
|
||||
const expireTime = createTimeObj.add(timeoutHours, 'hour');
|
||||
const now = dayjs();
|
||||
return now.isAfter(expireTime);
|
||||
};
|
||||
|
||||
const getInfiniteUlStyle = (showSearch: boolean = false): CSSProperties => ({
|
||||
marginTop: showSearch ? '0' : '0', // 如果显示搜索框,增加更多的上边距
|
||||
height: showSearch ? '75vh' : '84vh', // 相应调整高度
|
||||
width: '100%',
|
||||
padding: '0',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden'
|
||||
// 注意:小程序不支持 boxShadow
|
||||
})
|
||||
|
||||
// 统一的订单状态标签配置,与后端 statusFilter 保持一致
|
||||
const tabs = [
|
||||
{
|
||||
index: 0,
|
||||
key: '全部',
|
||||
title: '全部',
|
||||
description: '所有订单',
|
||||
statusFilter: -1 // 使用-1表示全部订单
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
key: '待付款',
|
||||
title: '待付款',
|
||||
description: '等待付款的订单',
|
||||
statusFilter: 0 // 对应后端:pay_status = false
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
key: '待发货',
|
||||
title: '待发货',
|
||||
description: '已付款待发货的订单',
|
||||
statusFilter: 1 // 对应后端:pay_status = true AND delivery_status = 10
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
key: '待收货',
|
||||
title: '待收货',
|
||||
description: '已发货待收货的订单',
|
||||
statusFilter: 3 // 对应后端:pay_status = true AND delivery_status = 20
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
key: '已完成',
|
||||
title: '已完成',
|
||||
description: '已完成的订单',
|
||||
statusFilter: 5 // 对应后端:order_status = 1
|
||||
},
|
||||
{
|
||||
index: 5,
|
||||
key: '退货/售后',
|
||||
title: '退货/售后',
|
||||
description: '退货/售后的订单',
|
||||
statusFilter: 6 // 对应后端:order_status = 6 (已退款)
|
||||
}
|
||||
]
|
||||
|
||||
// 扩展订单接口,包含商品信息
|
||||
interface OrderWithGoods extends ShopOrder {
|
||||
orderGoods?: ShopOrderGoods[];
|
||||
}
|
||||
|
||||
interface OrderListProps {
|
||||
onReload?: () => void;
|
||||
searchParams?: ShopOrderParam;
|
||||
showSearch?: boolean;
|
||||
}
|
||||
|
||||
function OrderList(props: OrderListProps) {
|
||||
const [list, setList] = useState<OrderWithGoods[]>([])
|
||||
const [page, setPage] = useState(1)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
// 根据传入的statusFilter设置初始tab索引
|
||||
const getInitialTabIndex = () => {
|
||||
if (props.searchParams?.statusFilter !== undefined) {
|
||||
const tab = tabs.find(t => t.statusFilter === props.searchParams?.statusFilter);
|
||||
return tab ? tab.index : 0;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const [tapIndex, setTapIndex] = useState<string | number>(() => {
|
||||
const initialIndex = getInitialTabIndex();
|
||||
console.log('初始化tapIndex:', initialIndex, '对应statusFilter:', props.searchParams?.statusFilter);
|
||||
return initialIndex;
|
||||
})
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
// 获取订单状态文本
|
||||
const getOrderStatusText = (order: ShopOrder) => {
|
||||
|
||||
// 优先检查订单状态
|
||||
if (order.orderStatus === 2) return '已取消';
|
||||
if (order.orderStatus === 4) return '退款申请中';
|
||||
if (order.orderStatus === 5) return '退款被拒绝';
|
||||
if (order.orderStatus === 6) return '退款成功';
|
||||
if (order.orderStatus === 7) return '客户端申请退款';
|
||||
|
||||
// 检查支付状态 (payStatus为boolean类型,false/0表示未付款,true/1表示已付款)
|
||||
if (!order.payStatus) return '等待买家付款';
|
||||
|
||||
// 已付款后检查发货状态
|
||||
if (order.deliveryStatus === 10) return '待发货';
|
||||
if (order.deliveryStatus === 20) return '待收货';
|
||||
if (order.deliveryStatus === 30) return '已完成';
|
||||
|
||||
// 最后检查订单完成状态
|
||||
if (order.orderStatus === 1) return '已完成';
|
||||
if (order.orderStatus === 0) return '未使用';
|
||||
|
||||
return '未知状态';
|
||||
};
|
||||
|
||||
// 获取订单状态颜色
|
||||
const getOrderStatusColor = (order: ShopOrder) => {
|
||||
// 优先检查订单状态
|
||||
if (order.orderStatus === 2) return 'text-gray-500'; // 已取消
|
||||
if (order.orderStatus === 4) return 'text-orange-500'; // 退款申请中
|
||||
if (order.orderStatus === 5) return 'text-red-500'; // 退款被拒绝
|
||||
if (order.orderStatus === 6) return 'text-green-500'; // 退款成功
|
||||
if (order.orderStatus === 7) return 'text-orange-500'; // 客户端申请退款
|
||||
|
||||
// 检查支付状态
|
||||
if (!order.payStatus) return 'text-orange-500'; // 等待买家付款
|
||||
|
||||
// 已付款后检查发货状态
|
||||
if (order.deliveryStatus === 10) return 'text-blue-500'; // 待发货
|
||||
if (order.deliveryStatus === 20) return 'text-purple-500'; // 待收货
|
||||
if (order.deliveryStatus === 30) return 'text-green-500'; // 已收货
|
||||
|
||||
// 最后检查订单完成状态
|
||||
if (order.orderStatus === 1) return 'text-green-600'; // 已完成
|
||||
if (order.orderStatus === 0) return 'text-gray-500'; // 未使用
|
||||
|
||||
return 'text-gray-600'; // 默认颜色
|
||||
};
|
||||
|
||||
// 使用后端统一的 statusFilter 进行筛选
|
||||
const getOrderStatusParams = (index: string | number) => {
|
||||
let params: ShopOrderParam = {};
|
||||
// 添加用户ID过滤
|
||||
params.userId = Taro.getStorageSync('UserId');
|
||||
|
||||
// 获取当前tab的statusFilter配置
|
||||
const currentTab = tabs.find(tab => tab.index === Number(index));
|
||||
if (currentTab && currentTab.statusFilter !== undefined) {
|
||||
params.statusFilter = currentTab.statusFilter;
|
||||
}
|
||||
// 注意:当statusFilter为undefined时,不要添加到params中,这样API请求就不会包含这个参数
|
||||
|
||||
console.log(`Tab ${index} (${currentTab?.title}) 筛选参数:`, params);
|
||||
return params;
|
||||
};
|
||||
|
||||
const reload = async (resetPage = false, targetPage?: number) => {
|
||||
setLoading(true);
|
||||
setError(null); // 清除之前的错误
|
||||
const currentPage = resetPage ? 1 : (targetPage || page);
|
||||
const statusParams = getOrderStatusParams(tapIndex);
|
||||
// 合并搜索条件,tab的statusFilter优先级更高
|
||||
const searchConditions: any = {
|
||||
page: currentPage,
|
||||
userId: statusParams.userId, // 用户ID
|
||||
...props.searchParams, // 搜索关键词等其他条件
|
||||
};
|
||||
|
||||
// statusFilter总是添加到搜索条件中(包括-1表示全部)
|
||||
if (statusParams.statusFilter !== undefined) {
|
||||
searchConditions.statusFilter = statusParams.statusFilter;
|
||||
}
|
||||
console.log('订单筛选条件:', {
|
||||
tapIndex,
|
||||
statusParams,
|
||||
searchConditions,
|
||||
finalStatusFilter: searchConditions.statusFilter
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await pageShopOrder(searchConditions);
|
||||
let newList: OrderWithGoods[];
|
||||
|
||||
if (res?.list && res?.list.length > 0) {
|
||||
// 批量获取订单商品信息,限制并发数量
|
||||
const batchSize = 3; // 限制并发数量为3
|
||||
const ordersWithGoods: OrderWithGoods[] = [];
|
||||
|
||||
for (let i = 0; i < res.list.length; i += batchSize) {
|
||||
const batch = res.list.slice(i, i + batchSize);
|
||||
const batchResults = await Promise.all(
|
||||
batch.map(async (order) => {
|
||||
try {
|
||||
const orderGoods = await listShopOrderGoods({orderId: order.orderId});
|
||||
return {
|
||||
...order,
|
||||
orderGoods: orderGoods || []
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取订单商品失败:', error);
|
||||
return {
|
||||
...order,
|
||||
orderGoods: []
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
ordersWithGoods.push(...batchResults);
|
||||
}
|
||||
|
||||
// 合并数据
|
||||
newList = resetPage ? ordersWithGoods : list?.concat(ordersWithGoods);
|
||||
|
||||
// 正确判断是否还有更多数据
|
||||
const hasMoreData = res.list.length >= 10; // 假设每页10条数据
|
||||
setHasMore(hasMoreData);
|
||||
} else {
|
||||
newList = resetPage ? [] : list;
|
||||
setHasMore(false);
|
||||
}
|
||||
|
||||
setList(newList || []);
|
||||
setPage(currentPage);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('加载订单失败:', error);
|
||||
setLoading(false);
|
||||
setError('加载订单失败,请重试');
|
||||
// 添加错误提示
|
||||
Taro.showToast({
|
||||
title: '加载失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const reloadMore = async () => {
|
||||
if (loading || !hasMore) return; // 防止重复加载
|
||||
const nextPage = page + 1;
|
||||
setPage(nextPage);
|
||||
await reload(false, nextPage);
|
||||
};
|
||||
|
||||
// 确认收货
|
||||
const confirmReceive = async (order: ShopOrder) => {
|
||||
try {
|
||||
await updateShopOrder({
|
||||
...order,
|
||||
deliveryStatus: 30, // 已收货
|
||||
orderStatus: 1 // 已完成
|
||||
});
|
||||
Taro.showToast({
|
||||
title: '确认收货成功',
|
||||
});
|
||||
await reload(true); // 重新加载列表
|
||||
props.onReload?.(); // 通知父组件刷新
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: '确认收货失败',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 取消订单
|
||||
const cancelOrder = async (order: ShopOrder) => {
|
||||
try {
|
||||
// 显示确认对话框
|
||||
const result = await Taro.showModal({
|
||||
title: '确认取消',
|
||||
content: '确定要取消这个订单吗?',
|
||||
confirmText: '确认取消',
|
||||
cancelText: '我再想想'
|
||||
});
|
||||
|
||||
if (!result.confirm) return;
|
||||
|
||||
// 更新订单状态为已取消,而不是删除订单
|
||||
await updateShopOrder({
|
||||
...order,
|
||||
orderStatus: 2 // 已取消
|
||||
});
|
||||
|
||||
Taro.showToast({
|
||||
title: '订单已取消',
|
||||
icon: 'success'
|
||||
});
|
||||
void reload(true); // 重新加载列表
|
||||
props.onReload?.(); // 通知父组件刷新
|
||||
} catch (error) {
|
||||
console.error('取消订单失败:', error);
|
||||
Taro.showToast({
|
||||
title: '取消订单失败',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
void reload(true); // 首次加载或tab切换时重置页码
|
||||
}, [tapIndex]); // 监听tapIndex变化
|
||||
|
||||
// 监听外部statusFilter变化,同步更新tab索引
|
||||
useEffect(() => {
|
||||
// 获取当前的statusFilter,如果未定义则默认为-1(全部)
|
||||
const currentStatusFilter = props.searchParams?.statusFilter !== undefined
|
||||
? props.searchParams.statusFilter
|
||||
: -1;
|
||||
|
||||
const tab = tabs.find(t => t.statusFilter === currentStatusFilter);
|
||||
const targetTabIndex = tab ? tab.index : 0;
|
||||
|
||||
console.log('外部statusFilter变化:', {
|
||||
statusFilter: currentStatusFilter,
|
||||
originalStatusFilter: props.searchParams?.statusFilter,
|
||||
currentTapIndex: tapIndex,
|
||||
targetTabIndex,
|
||||
shouldUpdate: targetTabIndex !== tapIndex
|
||||
});
|
||||
|
||||
if (targetTabIndex !== tapIndex) {
|
||||
setTapIndex(targetTabIndex);
|
||||
// 不需要调用reload,因为tapIndex变化会触发reload
|
||||
}
|
||||
}, [props.searchParams?.statusFilter]); // 监听statusFilter变化
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
align={'left'}
|
||||
className={'fixed left-0'}
|
||||
style={{
|
||||
zIndex: 998,
|
||||
borderBottom: '1px solid #e5e5e5'
|
||||
}}
|
||||
tabStyle={{
|
||||
backgroundColor: '#ffffff'
|
||||
// 注意:小程序不支持 boxShadow
|
||||
}}
|
||||
value={tapIndex}
|
||||
onChange={(paneKey) => {
|
||||
setTapIndex(paneKey)
|
||||
}}
|
||||
>
|
||||
{
|
||||
tabs?.map((item, _) => {
|
||||
return (
|
||||
<TabPane
|
||||
key={item.index}
|
||||
title={loading && tapIndex === item.index ? `${item.title}...` : item.title}
|
||||
></TabPane>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Tabs>
|
||||
<View style={getInfiniteUlStyle(props.showSearch)} id="scroll">
|
||||
{error ? (
|
||||
<View className="flex flex-col items-center justify-center h-64">
|
||||
<View className="text-gray-500 mb-4">{error}</View>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={() => reload(true)}
|
||||
>
|
||||
重新加载
|
||||
</Button>
|
||||
</View>
|
||||
) : (
|
||||
<InfiniteLoading
|
||||
target="scroll"
|
||||
hasMore={hasMore}
|
||||
onLoadMore={reloadMore}
|
||||
onScroll={() => {
|
||||
|
||||
}}
|
||||
onScrollToUpper={() => {
|
||||
|
||||
}}
|
||||
loadingText={
|
||||
<>
|
||||
加载中
|
||||
</>
|
||||
}
|
||||
loadMoreText={
|
||||
list.length === 0 ? (
|
||||
<Empty style={{backgroundColor: 'transparent'}} description="您还没有订单哦"/>
|
||||
) : (
|
||||
<View className={'h-24'}>
|
||||
没有更多了
|
||||
</View>
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
{/* 订单列表 */}
|
||||
{list.length > 0 && list
|
||||
?.filter((item) => {
|
||||
// 如果是待付款标签页(tapIndex === 0),过滤掉支付已过期的订单
|
||||
if (tapIndex === 0 && !item.payStatus && item.orderStatus !== 2 && item.createTime) {
|
||||
return !isPaymentExpired(item.createTime);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
?.map((item, index) => {
|
||||
return (
|
||||
<Cell key={index} style={{padding: '16px'}}
|
||||
onClick={() => Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${item.orderId}`})}>
|
||||
<Space direction={'vertical'} className={'w-full flex flex-col'}>
|
||||
<View className={'order-no flex justify-between'}>
|
||||
<View className={'flex items-center'}>
|
||||
<Text className={'text-gray-600 font-bold text-sm'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyText(`${item.orderNo}`)
|
||||
}}>{item.orderNo}</Text>
|
||||
</View>
|
||||
{/* 右侧显示合并的状态和倒计时 */}
|
||||
<View className={`${getOrderStatusColor(item)} font-medium`}>
|
||||
{!item.payStatus && item.orderStatus !== 2 ? (
|
||||
<PaymentCountdown
|
||||
createTime={item.createTime}
|
||||
payStatus={item.payStatus}
|
||||
realTime={false}
|
||||
showSeconds={false}
|
||||
mode={'badge'}
|
||||
/>
|
||||
) : (
|
||||
getOrderStatusText(item)
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
className={'create-time text-gray-400 text-xs'}>{dayjs(item.createTime).format('YYYY年MM月DD日 HH:mm:ss')}</View>
|
||||
|
||||
{/* 商品信息 */}
|
||||
<View className={'goods-info'}>
|
||||
{item.orderGoods && item.orderGoods.length > 0 ? (
|
||||
item.orderGoods.map((goods, goodsIndex) => (
|
||||
<View key={goodsIndex} className={'flex items-center mb-2'}>
|
||||
<Image
|
||||
src={goods.image || '/default-goods.png'}
|
||||
width="50"
|
||||
height="50"
|
||||
lazyLoad={false}
|
||||
className={'rounded'}
|
||||
/>
|
||||
<View className={'ml-2 flex flex-col flex-1'}>
|
||||
<Text className={'text-sm font-bold'}>{goods.goodsName}</Text>
|
||||
{goods.spec && <Text className={'text-gray-500 text-xs'}>规格:{goods.spec}</Text>}
|
||||
<Text className={'text-gray-500 text-xs'}>数量:{goods.totalNum}</Text>
|
||||
</View>
|
||||
<Text className={'text-sm'}>¥{goods.price}</Text>
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
<View className={'flex items-center'}>
|
||||
<Avatar
|
||||
src='/default-goods.png'
|
||||
size={'50'}
|
||||
shape={'square'}
|
||||
/>
|
||||
<View className={'ml-2'}>
|
||||
<Text className={'text-sm'}>{item.title || '订单商品'}</Text>
|
||||
<Text className={'text-gray-400 text-xs'}>{item.totalNum}件商品</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Text className={'w-full text-right'}>实付金额:¥{item.payPrice}</Text>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<Space className={'btn flex justify-end'}>
|
||||
{/* 待付款状态:显示取消订单和立即支付 */}
|
||||
{(!item.payStatus) && item.orderStatus !== 2 && (
|
||||
<Space>
|
||||
<Button size={'small'} onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
void cancelOrder(item);
|
||||
}}>取消订单</Button>
|
||||
<Button size={'small'} type="primary" onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
console.log('立即支付')
|
||||
}}>立即支付</Button>
|
||||
</Space>
|
||||
)}
|
||||
{/* 待收货状态:显示确认收货 */}
|
||||
{item.deliveryStatus === 20 && (
|
||||
<Button size={'small'} type="primary" onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
void confirmReceive(item);
|
||||
}}>确认收货</Button>
|
||||
)}
|
||||
{/* 已完成状态:显示申请退款 */}
|
||||
{item.orderStatus === 1 && (
|
||||
<Button size={'small'} onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
console.log('申请退款')
|
||||
}}>申请退款</Button>
|
||||
)}
|
||||
{/* 退款相关状态的按钮可以在这里添加 */}
|
||||
</Space>
|
||||
</Space>
|
||||
</Cell>
|
||||
)
|
||||
})}
|
||||
</InfiniteLoading>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrderList
|
||||
Reference in New Issue
Block a user