Browse Source

feat(user/order): 优化订单列表功能和性能

- 添加防抖搜索功能,提高搜索性能
- 优化筛选逻辑,保留初始状态筛选
- 重构订单数据加载逻辑,支持分批加载
- 完善错误处理和用户提示
- 优化取消订单功能,增加确认对话框
-调整订单状态显示逻辑
master
科技小王子 2 weeks ago
parent
commit
46a555b27a
  1. 196
      src/user/order/components/OrderList.tsx
  2. 38
      src/user/order/order.tsx

196
src/user/order/components/OrderList.tsx

@ -81,8 +81,25 @@ function OrderList(props: OrderListProps) {
const [list, setList] = useState<OrderWithGoods[]>([])
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
const [tapIndex, setTapIndex] = useState<string | number>(0)
// 根据传入的statusFilter设置初始tab索引
const getInitialTabIndex = () => {
if (props.searchParams?.statusFilter !== undefined) {
// 如果statusFilter为-1,表示全部,对应index为0
if (props.searchParams.statusFilter === -1) {
return 0;
}
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) => {
@ -149,9 +166,10 @@ function OrderList(props: OrderListProps) {
return params;
};
const reload = async (resetPage = false) => {
const reload = async (resetPage = false, targetPage?: number) => {
setLoading(true);
const currentPage = resetPage ? 1 : page;
setError(null); // 清除之前的错误
const currentPage = resetPage ? 1 : (targetPage || page);
const statusParams = getOrderStatusParams(tapIndex);
const searchConditions = {
page: currentPage,
@ -169,30 +187,40 @@ function OrderList(props: OrderListProps) {
let newList: OrderWithGoods[] = [];
if (res?.list && res?.list.length > 0) {
// 为每个订单获取商品信息
const ordersWithGoods = await Promise.all(
res.list.map(async (order) => {
try {
const orderGoods = await listShopOrderGoods({ orderId: order.orderId });
return {
...order,
orderGoods: orderGoods || []
};
} catch (error) {
console.error('获取订单商品失败:', error);
return {
...order,
orderGoods: []
};
}
})
);
// 批量获取订单商品信息,限制并发数量
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);
setHasMore(true);
// 正确判断是否还有更多数据
const hasMoreData = res.list.length >= 10; // 假设每页10条数据
setHasMore(hasMoreData);
} else {
newList = [];
newList = resetPage ? [] : list;
setHasMore(false);
}
@ -202,12 +230,20 @@ function OrderList(props: OrderListProps) {
} catch (error) {
console.error('加载订单失败:', error);
setLoading(false);
setError('加载订单失败,请重试');
// 添加错误提示
Taro.showToast({
title: '加载失败,请重试',
icon: 'none'
});
}
};
const reloadMore = async () => {
setPage(page + 1);
reload();
if (loading || !hasMore) return; // 防止重复加载
const nextPage = page + 1;
setPage(nextPage);
await reload(false, nextPage);
};
// 确认收货
@ -233,9 +269,25 @@ function OrderList(props: OrderListProps) {
// 取消订单
const cancelOrder = async (order: ShopOrder) => {
try {
await removeShopOrder(order.orderId);
// 显示确认对话框
const result = await Taro.showModal({
title: '确认取消',
content: '确定要取消这个订单吗?',
confirmText: '确认取消',
cancelText: '我再想想'
});
if (!result.confirm) return;
// 更新订单状态为已取消,而不是删除订单
await updateShopOrder({
...order,
orderStatus: 2 // 已取消
});
Taro.showToast({
title: '订单已删除',
title: '订单已取消',
icon: 'success'
});
reload(true).then(); // 重新加载列表
props.onReload?.(); // 通知父组件刷新
@ -243,6 +295,7 @@ function OrderList(props: OrderListProps) {
console.error('取消订单失败:', error);
Taro.showToast({
title: '取消订单失败',
icon: 'error'
});
}
};
@ -252,6 +305,30 @@ function OrderList(props: OrderListProps) {
}, [tapIndex]); // 监听tapIndex变化
useEffect(() => {
// 当外部传入的statusFilter改变时,更新对应的tab索引
if (props.searchParams?.statusFilter !== undefined) {
let targetTabIndex = 0;
// 如果statusFilter为-1,表示全部,对应index为0
if (props.searchParams.statusFilter === -1) {
targetTabIndex = 0;
} else {
const tab = tabs.find(t => t.statusFilter === props.searchParams?.statusFilter);
targetTabIndex = tab ? tab.index : 0;
}
console.log('searchParams变化:', {
statusFilter: props.searchParams.statusFilter,
currentTapIndex: tapIndex,
targetTabIndex,
shouldUpdate: targetTabIndex !== tapIndex
});
if (targetTabIndex !== tapIndex) {
setTapIndex(targetTabIndex);
return; // 避免重复调用reload
}
}
reload(true).then(); // 搜索参数变化时重置页码
}, [props.searchParams]); // 监听搜索参数变化
@ -278,36 +355,48 @@ function OrderList(props: OrderListProps) {
tabs?.map((item, index) => {
return (
<TabPane
key={index}
title={loading && tapIndex === index ? `${item.title}...` : item.title}
key={item.index}
title={loading && tapIndex === item.index ? `${item.title}...` : item.title}
></TabPane>
)
})
}
</Tabs>
<div style={getInfiniteUlStyle(props.showSearch)} id="scroll">
<InfiniteLoading
target="scroll"
hasMore={hasMore}
onLoadMore={reloadMore}
onScroll={() => {
}}
onScrollToUpper={() => {
}}
loadingText={
<>
</>
}
loadMoreText={
<>
</>
}
>
{list?.map((item, index) => {
{error ? (
<div className="flex flex-col items-center justify-center h-64">
<div className="text-gray-500 mb-4">{error}</div>
<Button
size="small"
type="primary"
onClick={() => reload(true)}
>
</Button>
</div>
) : (
<InfiniteLoading
target="scroll"
hasMore={hasMore}
onLoadMore={reloadMore}
onScroll={() => {
}}
onScrollToUpper={() => {
}}
loadingText={
<>
</>
}
loadMoreText={
<>
</>
}
>
{list?.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'}>
@ -378,8 +467,9 @@ function OrderList(props: OrderListProps) {
</Space>
</Cell>
)
})}
</InfiniteLoading>
})}
</InfiniteLoading>
)}
</div>
</>
)

38
src/user/order/order.tsx

@ -1,4 +1,4 @@
import {useState} from "react";
import {useState, useCallback, useRef} from "react";
import Taro, {useDidShow} from '@tarojs/taro'
import {Space, NavBar, Button, Input} from '@nutui/nutui-react-taro'
import {Search, Filter, ArrowLeft} from '@nutui/icons-react-taro'
@ -10,18 +10,32 @@ import './order.scss'
function Order() {
const {params} = useRouter();
const [statusBarHeight, setStatusBarHeight] = useState<number>()
const [statusBarHeight, setStatusBarHeight] = useState<number>(0) // 默认值为0
const [searchParams, setSearchParams] = useState<ShopOrderParam>({
statusFilter: params.statusFilter != undefined && params.statusFilter != '' ? parseInt(params.statusFilter) : -1
})
const [showSearch, setShowSearch] = useState(false)
const [searchKeyword, setSearchKeyword] = useState('')
const searchTimeoutRef = useRef<NodeJS.Timeout>()
const reload = async (where?: ShopOrderParam) => {
console.log(where,'where...')
setSearchParams(prev => ({ ...prev, ...where }))
}
// 防抖搜索函数
const debouncedSearch = useCallback((keyword: string) => {
if (searchTimeoutRef.current) {
clearTimeout(searchTimeoutRef.current);
}
searchTimeoutRef.current = setTimeout(() => {
if (keyword.trim()) {
handleSearch({keywords: keyword.trim()});
}
}, 500); // 500ms防抖延迟
}, []);
// 处理搜索
const handleSearch = (where: ShopOrderParam) => {
setSearchParams(where)
@ -30,7 +44,10 @@ function Order() {
// 重置搜索
const handleResetSearch = () => {
setSearchParams({})
setSearchKeyword(''); // 清空搜索关键词
setSearchParams({
statusFilter: params.statusFilter != undefined && params.statusFilter != '' ? parseInt(params.statusFilter) : -1
}); // 重置搜索参数,但保留初始状态筛选
reload().then()
}
@ -56,10 +73,10 @@ function Order() {
return (
<View className="bg-gray-50 min-h-screen">
<View style={{height: `${statusBarHeight}px`, backgroundColor: '#ffffff'}}></View>
<View style={{height: `${statusBarHeight || 0}px`, backgroundColor: '#ffffff'}}></View>
<NavBar
fixed={true}
style={{marginTop: `${statusBarHeight}px`, backgroundColor: '#ffffff'}}
style={{marginTop: `${statusBarHeight || 0}px`, backgroundColor: '#ffffff'}}
left={
<>
<div className={'flex justify-between items-center w-full'}>
@ -80,16 +97,12 @@ function Order() {
{/* 搜索和筛选工具栏 */}
<View className="bg-white px-4 py-3 flex justify-between items-center border-b border-gray-100">
<View className="flex items-center">
<Search
size={18}
className="mr-3 text-gray-600"
onClick={() => setShowSearch(!showSearch)}
/>
<Filter
size={18}
className="text-gray-600"
onClick={() => setShowSearch(!showSearch)}
/>
<span className="ml-2 text-sm text-gray-600"></span>
</View>
</View>
@ -101,7 +114,10 @@ function Order() {
<Input
placeholder="搜索订单号、商品名称"
value={searchKeyword}
onChange={setSearchKeyword}
onChange={(value) => {
setSearchKeyword(value);
debouncedSearch(value); // 使用防抖搜索
}}
onConfirm={() => {
if (searchKeyword.trim()) {
handleSearch({keywords: searchKeyword.trim()});

Loading…
Cancel
Save