Files
xinlong-shop-taro/src/pages/order/list.tsx
赵忠林 ad3c209fe5 feat(order): 优化订单备注结构及线下付款状态显示
- 门店订单修改金额时,修改原因从追加 comments 改为保存 merchantRemarks 字段
- ShopOrder 和 OrderCreateRequest 接口新增 buyerRemarks 和 merchantRemarks 字段,实现订单备注字段分离
- 结算页由 comments 注入变更为 buyerRemarks,门店订单列表新增买家备注展示块
- 修复线下付款订单状态显示:统一线下付款(payType=9)与货到付款(payType=8)的待发货状态显示
- 取消按钮逻辑优化,已付款订单不显示取消按钮,文案由"关闭订单"改为"取消订单"
- 订单列表和详情页弹窗标题及提示文案更新,确保状态描述准确,提升用户体验
2026-07-15 03:07:35 +08:00

258 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect, useCallback, useRef } from 'react'
import { View, Text, ScrollView } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { pageShopOrder, updateShopOrder } from '@/api/shop/shopOrder'
import type { ShopOrder, ShopOrderParam } from '@/api/shop/shopOrder/model'
import { useUser } from '@/hooks/useUser'
import { OrderListStatus, OrderListTabText, OrderStatus } from '@/types/order'
import OrderCard from '@/components/common/OrderCard'
import EmptyState from '@/components/common/EmptyState'
import LoadMore from '@/components/common/LoadMore'
import { requireLogin } from '@/utils/login-guard'
definePageConfig({
navigationBarTitleText: '我的订单',
})
// Tab 配置(使用后端 statusFilter 字段)
const tabList = [
{ title: OrderListTabText[OrderListStatus.All], status: OrderListStatus.All },
{ title: OrderListTabText[OrderListStatus.Unpaid], status: OrderListStatus.Unpaid },
{ title: OrderListTabText[OrderListStatus.Unshipped], status: OrderListStatus.Unshipped },
{ title: OrderListTabText[OrderListStatus.Shipped], status: OrderListStatus.Shipped },
{ title: OrderListTabText[OrderListStatus.Completed], status: OrderListStatus.Completed },
]
interface TabState {
orders: ShopOrder[]
page: number
finished: boolean
loading: boolean
initialized: boolean
}
const OrderListPage: React.FC = () => {
const { user, isLoggedIn } = useUser()
const initTab = Taro.getCurrentInstance().router?.params?.tab
?? Taro.getStorageSync('order_tab')
// 读取后清除,避免影响下次进入
if (Taro.getStorageSync('order_tab') !== '') {
Taro.removeStorageSync('order_tab')
}
const [tabIndex, setTabIndex] = useState(Number(initTab) || 0)
// 每个 tab 独立维护状态
const [tabStates, setTabStates] = useState<TabState[]>(
tabList.map(() => ({
orders: [],
page: 1,
finished: false,
loading: false,
initialized: false,
}))
)
const currentState = tabStates[tabIndex]
// 用 ref 避免 loadOrders 闭包中捕获过期状态
const tabStatesRef = useRef(tabStates)
tabStatesRef.current = tabStates
// userId 也用 ref防止闭包过期导致登录后仍传 undefined
const userIdRef = useRef(user?.userId)
userIdRef.current = user?.userId
const updateTabState = useCallback(
(index: number, partial: Partial<TabState>) => {
setTabStates(prev => {
const next = [...prev]
next[index] = { ...next[index], ...partial }
return next
})
},
[]
)
const loadOrders = useCallback(
async (targetIndex: number, p: number) => {
// 没有当前登录用户 userId禁止加载防止看到别人的订单
const userId = userIdRef.current
if (!userId) return
const state = tabStatesRef.current[targetIndex]
if (state.loading) return
updateTabState(targetIndex, { loading: true })
try {
const params: ShopOrderParam = { page: p, limit: 10, userId }
const status = tabList[targetIndex]?.status
// 使用后端 statusFilter 字段进行筛选
// -1=全部, 0=待支付, 1=待发货, 3=待收货, 5=已完成
if (status !== undefined && status !== OrderListStatus.All) {
params.statusFilter = status
}
const res = await pageShopOrder(params)
const list = res?.list || []
updateTabState(targetIndex, {
orders: p === 1 ? list : [...state.orders, ...list],
page: p,
finished: list.length < 10,
loading: false,
initialized: true,
})
} catch {
updateTabState(targetIndex, { loading: false, initialized: true })
}
},
[updateTabState]
)
// 切换 tab 时加载数据
useEffect(() => {
if (!isLoggedIn) {
requireLogin({ action: 'viewOrder', redirect: '/pages/order/list', content: '登录后才能查看订单,是否前往登录?' })
return
}
// 已登录但 userId 还没拿到,等下次渲染再加载
if (!user?.userId) return
if (!tabStates[tabIndex].initialized) {
loadOrders(tabIndex, 1)
}
}, [tabIndex, isLoggedIn, user?.userId])
// 页面每次显示时刷新数据tabBar 页 switchTab 不会重新挂载组件)
const isFirstShow = useRef(true)
Taro.useDidShow(() => {
// 首次显示由 useEffect 处理,跳过
if (isFirstShow.current) {
isFirstShow.current = false
return
}
const userId = userIdRef.current
if (!userId) return
// 标记所有 tab 为未初始化,切换时会重新加载
setTabStates(prev => prev.map(s => ({ ...s, initialized: false })))
// 立即刷新当前 tab
loadOrders(tabIndex, 1)
})
const handleLoadMore = () => {
if (!currentState.finished && !currentState.loading) {
loadOrders(tabIndex, currentState.page + 1)
}
}
/** 取消订单(未付款/待确认收款状态下允许客户取消) */
const handleCloseOrder = (order: ShopOrder) => {
Taro.showModal({
title: '确认取消订单',
content: '取消后订单将关闭,是否确认?',
confirmColor: '#ee0a24',
success: async (res) => {
if (res.confirm && order?.orderId) {
try {
await updateShopOrder({ ...order, orderStatus: OrderStatus.Cancelled })
Taro.showToast({ title: '订单已取消', icon: 'success' })
// 标记所有 tab 为未初始化,切换时会重新加载(确保「全部」等 tab 能看到最新状态)
setTabStates(prev => prev.map(s => ({ ...s, initialized: false })))
// 立即刷新当前 tab
loadOrders(tabIndex, 1)
} catch (err: any) {
Taro.showToast({ title: err?.message || '取消失败', icon: 'none' })
}
}
}
})
}
const handleTabChange = (index: number) => {
if (index !== tabIndex) {
setTabIndex(index)
}
}
if (!isLoggedIn) {
return null
}
return (
<View className='min-h-screen bg-gray-50 flex flex-col'>
{/* 自定义 Tabs 标题栏 */}
<View className='bg-white flex flex-row items-center border-b border-gray-100'>
{tabList.map((tab, index) => (
<View
key={tab.title}
className='flex-1 flex flex-col items-center justify-center py-3'
onClick={() => handleTabChange(index)}
>
<Text
className='text-sm font-medium'
style={{
color: index === tabIndex ? '#ee0a24' : '#666',
}}
>
{tab.title}
</Text>
{index === tabIndex && (
<View
className='mt-1 rounded-full'
style={{
width: '20px',
height: '3px',
backgroundColor: '#ee0a24',
}}
/>
)}
</View>
))}
</View>
{/* 内容区域 - 每个 tab 独立渲染 */}
<View className='flex-1 relative'>
{tabList.map((tab, idx) => (
<View
key={tab.title}
className='absolute inset-0'
style={{
display: idx === tabIndex ? 'flex' : 'none',
flexDirection: 'column',
}}
>
<ScrollView
scrollY
className='flex-1'
onScrollToLower={handleLoadMore}
lowerThreshold={100}
>
<View className='p-3'>
{tabStates[idx].orders.length > 0 ? (
tabStates[idx].orders.map(order => (
<OrderCard
key={order.orderId}
order={order}
onCloseOrder={handleCloseOrder}
/>
))
) : tabStates[idx].initialized && !tabStates[idx].loading ? (
<EmptyState text='暂无订单' />
) : null}
<LoadMore
loading={tabStates[idx].loading}
finished={tabStates[idx].finished}
/>
</View>
</ScrollView>
</View>
))}
</View>
</View>
)
}
export default OrderListPage