forked from gxwebsoft/mp-10550
feat(user/order): 优化订单列表功能和性能
- 添加防抖搜索功能,提高搜索性能 - 优化筛选逻辑,保留初始状态筛选 - 重构订单数据加载逻辑,支持分批加载 - 完善错误处理和用户提示 - 优化取消订单功能,增加确认对话框 -调整订单状态显示逻辑
This commit is contained in:
@@ -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={() => {
|
||||
{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={() => {
|
||||
}}
|
||||
onScrollToUpper={() => {
|
||||
|
||||
}}
|
||||
loadingText={
|
||||
<>
|
||||
加载中
|
||||
</>
|
||||
}
|
||||
loadMoreText={
|
||||
<>
|
||||
没有更多了
|
||||
</>
|
||||
}
|
||||
>
|
||||
{list?.map((item, index) => {
|
||||
}}
|
||||
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>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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()});
|
||||
|
||||
Reference in New Issue
Block a user