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 [list, setList] = useState<OrderWithGoods[]>([])
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
const [hasMore, setHasMore] = useState(true)
|
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 [loading, setLoading] = useState(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
// 获取订单状态文本
|
// 获取订单状态文本
|
||||||
const getOrderStatusText = (order: ShopOrder) => {
|
const getOrderStatusText = (order: ShopOrder) => {
|
||||||
@@ -149,9 +166,10 @@ function OrderList(props: OrderListProps) {
|
|||||||
return params;
|
return params;
|
||||||
};
|
};
|
||||||
|
|
||||||
const reload = async (resetPage = false) => {
|
const reload = async (resetPage = false, targetPage?: number) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const currentPage = resetPage ? 1 : page;
|
setError(null); // 清除之前的错误
|
||||||
|
const currentPage = resetPage ? 1 : (targetPage || page);
|
||||||
const statusParams = getOrderStatusParams(tapIndex);
|
const statusParams = getOrderStatusParams(tapIndex);
|
||||||
const searchConditions = {
|
const searchConditions = {
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
@@ -169,9 +187,14 @@ function OrderList(props: OrderListProps) {
|
|||||||
let newList: OrderWithGoods[] = [];
|
let newList: OrderWithGoods[] = [];
|
||||||
|
|
||||||
if (res?.list && res?.list.length > 0) {
|
if (res?.list && res?.list.length > 0) {
|
||||||
// 为每个订单获取商品信息
|
// 批量获取订单商品信息,限制并发数量
|
||||||
const ordersWithGoods = await Promise.all(
|
const batchSize = 3; // 限制并发数量为3
|
||||||
res.list.map(async (order) => {
|
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 {
|
try {
|
||||||
const orderGoods = await listShopOrderGoods({ orderId: order.orderId });
|
const orderGoods = await listShopOrderGoods({ orderId: order.orderId });
|
||||||
return {
|
return {
|
||||||
@@ -187,12 +210,17 @@ function OrderList(props: OrderListProps) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
ordersWithGoods.push(...batchResults);
|
||||||
|
}
|
||||||
|
|
||||||
// 合并数据
|
// 合并数据
|
||||||
newList = resetPage ? ordersWithGoods : list?.concat(ordersWithGoods);
|
newList = resetPage ? ordersWithGoods : list?.concat(ordersWithGoods);
|
||||||
setHasMore(true);
|
|
||||||
|
// 正确判断是否还有更多数据
|
||||||
|
const hasMoreData = res.list.length >= 10; // 假设每页10条数据
|
||||||
|
setHasMore(hasMoreData);
|
||||||
} else {
|
} else {
|
||||||
newList = [];
|
newList = resetPage ? [] : list;
|
||||||
setHasMore(false);
|
setHasMore(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,12 +230,20 @@ function OrderList(props: OrderListProps) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载订单失败:', error);
|
console.error('加载订单失败:', error);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
setError('加载订单失败,请重试');
|
||||||
|
// 添加错误提示
|
||||||
|
Taro.showToast({
|
||||||
|
title: '加载失败,请重试',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const reloadMore = async () => {
|
const reloadMore = async () => {
|
||||||
setPage(page + 1);
|
if (loading || !hasMore) return; // 防止重复加载
|
||||||
reload();
|
const nextPage = page + 1;
|
||||||
|
setPage(nextPage);
|
||||||
|
await reload(false, nextPage);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 确认收货
|
// 确认收货
|
||||||
@@ -233,9 +269,25 @@ function OrderList(props: OrderListProps) {
|
|||||||
// 取消订单
|
// 取消订单
|
||||||
const cancelOrder = async (order: ShopOrder) => {
|
const cancelOrder = async (order: ShopOrder) => {
|
||||||
try {
|
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({
|
Taro.showToast({
|
||||||
title: '订单已删除',
|
title: '订单已取消',
|
||||||
|
icon: 'success'
|
||||||
});
|
});
|
||||||
reload(true).then(); // 重新加载列表
|
reload(true).then(); // 重新加载列表
|
||||||
props.onReload?.(); // 通知父组件刷新
|
props.onReload?.(); // 通知父组件刷新
|
||||||
@@ -243,6 +295,7 @@ function OrderList(props: OrderListProps) {
|
|||||||
console.error('取消订单失败:', error);
|
console.error('取消订单失败:', error);
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '取消订单失败',
|
title: '取消订单失败',
|
||||||
|
icon: 'error'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -252,6 +305,30 @@ function OrderList(props: OrderListProps) {
|
|||||||
}, [tapIndex]); // 监听tapIndex变化
|
}, [tapIndex]); // 监听tapIndex变化
|
||||||
|
|
||||||
useEffect(() => {
|
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(); // 搜索参数变化时重置页码
|
reload(true).then(); // 搜索参数变化时重置页码
|
||||||
}, [props.searchParams]); // 监听搜索参数变化
|
}, [props.searchParams]); // 监听搜索参数变化
|
||||||
|
|
||||||
@@ -278,14 +355,26 @@ function OrderList(props: OrderListProps) {
|
|||||||
tabs?.map((item, index) => {
|
tabs?.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<TabPane
|
<TabPane
|
||||||
key={index}
|
key={item.index}
|
||||||
title={loading && tapIndex === index ? `${item.title}...` : item.title}
|
title={loading && tapIndex === item.index ? `${item.title}...` : item.title}
|
||||||
></TabPane>
|
></TabPane>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div style={getInfiniteUlStyle(props.showSearch)} id="scroll">
|
<div style={getInfiniteUlStyle(props.showSearch)} id="scroll">
|
||||||
|
{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
|
<InfiniteLoading
|
||||||
target="scroll"
|
target="scroll"
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
@@ -380,6 +469,7 @@ function OrderList(props: OrderListProps) {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</InfiniteLoading>
|
</InfiniteLoading>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {useState} from "react";
|
import {useState, useCallback, useRef} from "react";
|
||||||
import Taro, {useDidShow} from '@tarojs/taro'
|
import Taro, {useDidShow} from '@tarojs/taro'
|
||||||
import {Space, NavBar, Button, Input} from '@nutui/nutui-react-taro'
|
import {Space, NavBar, Button, Input} from '@nutui/nutui-react-taro'
|
||||||
import {Search, Filter, ArrowLeft} from '@nutui/icons-react-taro'
|
import {Search, Filter, ArrowLeft} from '@nutui/icons-react-taro'
|
||||||
@@ -10,18 +10,32 @@ import './order.scss'
|
|||||||
|
|
||||||
function Order() {
|
function Order() {
|
||||||
const {params} = useRouter();
|
const {params} = useRouter();
|
||||||
const [statusBarHeight, setStatusBarHeight] = useState<number>()
|
const [statusBarHeight, setStatusBarHeight] = useState<number>(0) // 默认值为0
|
||||||
const [searchParams, setSearchParams] = useState<ShopOrderParam>({
|
const [searchParams, setSearchParams] = useState<ShopOrderParam>({
|
||||||
statusFilter: params.statusFilter != undefined && params.statusFilter != '' ? parseInt(params.statusFilter) : -1
|
statusFilter: params.statusFilter != undefined && params.statusFilter != '' ? parseInt(params.statusFilter) : -1
|
||||||
})
|
})
|
||||||
const [showSearch, setShowSearch] = useState(false)
|
const [showSearch, setShowSearch] = useState(false)
|
||||||
const [searchKeyword, setSearchKeyword] = useState('')
|
const [searchKeyword, setSearchKeyword] = useState('')
|
||||||
|
const searchTimeoutRef = useRef<NodeJS.Timeout>()
|
||||||
|
|
||||||
const reload = async (where?: ShopOrderParam) => {
|
const reload = async (where?: ShopOrderParam) => {
|
||||||
console.log(where,'where...')
|
console.log(where,'where...')
|
||||||
setSearchParams(prev => ({ ...prev, ...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) => {
|
const handleSearch = (where: ShopOrderParam) => {
|
||||||
setSearchParams(where)
|
setSearchParams(where)
|
||||||
@@ -30,7 +44,10 @@ function Order() {
|
|||||||
|
|
||||||
// 重置搜索
|
// 重置搜索
|
||||||
const handleResetSearch = () => {
|
const handleResetSearch = () => {
|
||||||
setSearchParams({})
|
setSearchKeyword(''); // 清空搜索关键词
|
||||||
|
setSearchParams({
|
||||||
|
statusFilter: params.statusFilter != undefined && params.statusFilter != '' ? parseInt(params.statusFilter) : -1
|
||||||
|
}); // 重置搜索参数,但保留初始状态筛选
|
||||||
reload().then()
|
reload().then()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,10 +73,10 @@ function Order() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="bg-gray-50 min-h-screen">
|
<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
|
<NavBar
|
||||||
fixed={true}
|
fixed={true}
|
||||||
style={{marginTop: `${statusBarHeight}px`, backgroundColor: '#ffffff'}}
|
style={{marginTop: `${statusBarHeight || 0}px`, backgroundColor: '#ffffff'}}
|
||||||
left={
|
left={
|
||||||
<>
|
<>
|
||||||
<div className={'flex justify-between items-center w-full'}>
|
<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="bg-white px-4 py-3 flex justify-between items-center border-b border-gray-100">
|
||||||
<View className="flex items-center">
|
<View className="flex items-center">
|
||||||
<Search
|
|
||||||
size={18}
|
|
||||||
className="mr-3 text-gray-600"
|
|
||||||
onClick={() => setShowSearch(!showSearch)}
|
|
||||||
/>
|
|
||||||
<Filter
|
<Filter
|
||||||
size={18}
|
size={18}
|
||||||
className="text-gray-600"
|
className="text-gray-600"
|
||||||
onClick={() => setShowSearch(!showSearch)}
|
onClick={() => setShowSearch(!showSearch)}
|
||||||
/>
|
/>
|
||||||
|
<span className="ml-2 text-sm text-gray-600">筛选</span>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -101,7 +114,10 @@ function Order() {
|
|||||||
<Input
|
<Input
|
||||||
placeholder="搜索订单号、商品名称"
|
placeholder="搜索订单号、商品名称"
|
||||||
value={searchKeyword}
|
value={searchKeyword}
|
||||||
onChange={setSearchKeyword}
|
onChange={(value) => {
|
||||||
|
setSearchKeyword(value);
|
||||||
|
debouncedSearch(value); // 使用防抖搜索
|
||||||
|
}}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
if (searchKeyword.trim()) {
|
if (searchKeyword.trim()) {
|
||||||
handleSearch({keywords: searchKeyword.trim()});
|
handleSearch({keywords: searchKeyword.trim()});
|
||||||
|
|||||||
Reference in New Issue
Block a user