261 lines
9.2 KiB
TypeScript
261 lines
9.2 KiB
TypeScript
import { useCallback, useRef, useState } from 'react'
|
||
import Taro, { useDidShow, useReachBottom, useRouter } from '@tarojs/taro'
|
||
import { View, Text, ScrollView } from '@tarojs/components'
|
||
import { Empty, Popup } from '@antmjs/vantui'
|
||
import useAuthStore from '@/stores/auth/useAuthStore'
|
||
import { cancelOrderApi, getOrderDetailApi, getOrderListApi } from '@/services/order'
|
||
import { ORDER_STATUS_FILTERS, ORDER_STATUS_TEXT } from '@/types/order'
|
||
import type { OrderDetail, OrderListItem, OrderStatus } from '@/types/order'
|
||
import './index.less'
|
||
|
||
const PAGE_SIZE = 10
|
||
|
||
/**
|
||
* 订单列表页
|
||
* 入口:/pages/order-list/index?status=0|1|2|3|4|9|all
|
||
* 状态文案一律使用接口返回的 status_name(详情接口无该字段时沿用列表行)
|
||
*/
|
||
export default function OrderListPage() {
|
||
const router = useRouter()
|
||
const token = useAuthStore(s => s.token)
|
||
|
||
/** 当前状态筛选(undefined = 全部) */
|
||
const [status, setStatus] = useState<OrderStatus | undefined>(undefined)
|
||
const [orders, setOrders] = useState<OrderListItem[]>([])
|
||
const [page, setPage] = useState(1)
|
||
const [loading, setLoading] = useState(false)
|
||
const [finished, setFinished] = useState(false)
|
||
const loadingRef = useRef(false)
|
||
|
||
/** 订单详情弹层(statusName 沿用列表行,详情接口不返回 status_name) */
|
||
const [showDetail, setShowDetail] = useState(false)
|
||
const [orderDetail, setOrderDetail] = useState<OrderDetail | null>(null)
|
||
const [detailStatusName, setDetailStatusName] = useState('')
|
||
|
||
const loggedIn = !!token
|
||
|
||
/** 解析路由参数中的 status('all' / 数字 / 缺省 → undefined) */
|
||
const parseStatus = useCallback((raw?: string): OrderStatus | undefined => {
|
||
if (!raw || raw === 'all' || raw === '') return undefined
|
||
const num = Number(raw)
|
||
return Number.isNaN(num) ? undefined : (num as OrderStatus)
|
||
}, [])
|
||
|
||
/** 拉取订单列表 */
|
||
const loadOrders = useCallback(
|
||
async (pageNum: number, reset: boolean, statusParam?: OrderStatus) => {
|
||
if (!loggedIn || loadingRef.current) return
|
||
loadingRef.current = true
|
||
setLoading(true)
|
||
try {
|
||
const res = await getOrderListApi({
|
||
status: statusParam,
|
||
page: pageNum,
|
||
pageSize: PAGE_SIZE,
|
||
})
|
||
const { data, total } = res.data
|
||
setOrders(prev => (reset ? data : [...prev, ...data]))
|
||
setPage(pageNum)
|
||
setFinished(pageNum * PAGE_SIZE >= total)
|
||
} catch {
|
||
// 错误已由 request 层 toast
|
||
} finally {
|
||
loadingRef.current = false
|
||
setLoading(false)
|
||
}
|
||
},
|
||
[loggedIn],
|
||
)
|
||
|
||
useDidShow(() => {
|
||
const statusParam = parseStatus(router.params.status)
|
||
setStatus(statusParam)
|
||
loadOrders(1, true, statusParam)
|
||
})
|
||
|
||
useReachBottom(() => {
|
||
if (!finished && !loadingRef.current && loggedIn) {
|
||
loadOrders(page + 1, false, status)
|
||
}
|
||
})
|
||
|
||
/** 切换状态筛选 */
|
||
const handleStatusTap = useCallback(
|
||
(value?: OrderStatus) => {
|
||
setStatus(value)
|
||
setFinished(false)
|
||
loadOrders(1, true, value)
|
||
},
|
||
[loadOrders],
|
||
)
|
||
|
||
/** 查看订单详情 */
|
||
const handleOrderTap = useCallback(async (order: OrderListItem) => {
|
||
try {
|
||
const res = await getOrderDetailApi(order.id)
|
||
setOrderDetail(res.data)
|
||
setDetailStatusName(order.status_name)
|
||
setShowDetail(true)
|
||
} catch {
|
||
// 错误已由 request 层 toast
|
||
}
|
||
}, [])
|
||
|
||
/** 取消订单(仅待接单可取消,以 can_cancel 为准) */
|
||
const handleCancelOrder = useCallback(
|
||
(order: OrderListItem) => {
|
||
Taro.showModal({
|
||
title: '取消订单',
|
||
content: `确定取消订单 ${order.order_no} 吗?`,
|
||
confirmColor: '#ee0a24',
|
||
success: res => {
|
||
if (res.confirm) {
|
||
cancelOrderApi(order.id)
|
||
.then(() => {
|
||
Taro.showToast({ title: '订单已取消', icon: 'success' })
|
||
loadOrders(1, true, status)
|
||
})
|
||
.catch(() => {})
|
||
}
|
||
},
|
||
})
|
||
},
|
||
[loadOrders, status],
|
||
)
|
||
|
||
/** 跳转关联账单详情 */
|
||
const handleBillTap = useCallback((billId: number) => {
|
||
Taro.navigateTo({ url: `/pages/bill-detail/index?id=${billId}` })
|
||
}, [])
|
||
|
||
const goLogin = useCallback(() => {
|
||
Taro.navigateTo({ url: '/pages/login/index' })
|
||
}, [])
|
||
|
||
return (
|
||
<View className='order-list-page'>
|
||
{/* ========== 状态筛选 ========== */}
|
||
<ScrollView scrollX className='status-scroll'>
|
||
{ORDER_STATUS_FILTERS.map(item => (
|
||
<View
|
||
key={item.label}
|
||
className={`status-chip ${status === item.value ? 'active' : ''}`}
|
||
onClick={() => handleStatusTap(item.value)}
|
||
>
|
||
<Text>{item.label}</Text>
|
||
</View>
|
||
))}
|
||
</ScrollView>
|
||
|
||
{/* ========== 订单列表 ========== */}
|
||
{!loggedIn ? (
|
||
<Empty description='登录后查看订单' className='order-empty'>
|
||
<View className='order-empty__btn' onClick={goLogin}>去登录</View>
|
||
</Empty>
|
||
) : orders.length === 0 ? (
|
||
loading ? (
|
||
<View className='order-loading'><Text>加载中...</Text></View>
|
||
) : (
|
||
<Empty description='暂无订单' className='order-empty' />
|
||
)
|
||
) : (
|
||
orders.map(order => (
|
||
<View key={order.id} className='order-item' onClick={() => handleOrderTap(order)}>
|
||
<View className='order-item__header'>
|
||
<Text className='order-item__no'>{order.order_no}</Text>
|
||
<Text className={`order-item__status order-item__status--${order.status}`}>
|
||
{order.status_name}
|
||
</Text>
|
||
</View>
|
||
{/* 商品预览(仅前 3 条,完整明细见详情) */}
|
||
<Text className='order-item__preview'>
|
||
{order.items.map(i => `${i.product_name}×${i.quantity}`).join('、')}
|
||
{order.item_count > 3 ? ` 等${order.item_count}种` : ''}
|
||
</Text>
|
||
<View className='order-item__body'>
|
||
<Text className='order-item__date'>订货日期 {order.order_date}</Text>
|
||
<View className='order-item__amounts'>
|
||
<Text className='order-item__qty'>共 {order.total_quantity} 件</Text>
|
||
<Text className='order-item__amount'>¥{order.total_amount}</Text>
|
||
</View>
|
||
</View>
|
||
{order.remark && <Text className='order-item__remark'>备注:{order.remark}</Text>}
|
||
{(order.can_cancel || order.bill_id > 0) && (
|
||
<View className='order-item__footer'>
|
||
{order.bill_id > 0 && (
|
||
<View
|
||
className='order-item__bill'
|
||
onClick={e => {
|
||
e.stopPropagation()
|
||
handleBillTap(order.bill_id)
|
||
}}
|
||
>
|
||
查看账单
|
||
</View>
|
||
)}
|
||
{order.can_cancel && (
|
||
<View
|
||
className='order-item__cancel'
|
||
onClick={e => {
|
||
e.stopPropagation()
|
||
handleCancelOrder(order)
|
||
}}
|
||
>
|
||
取消订单
|
||
</View>
|
||
)}
|
||
</View>
|
||
)}
|
||
</View>
|
||
))
|
||
)}
|
||
|
||
{loggedIn && finished && orders.length > 0 && (
|
||
<View className='order-loading'><Text>没有更多了</Text></View>
|
||
)}
|
||
|
||
{/* ========== 订单详情弹层 ========== */}
|
||
<Popup
|
||
show={showDetail}
|
||
position='bottom'
|
||
round
|
||
closeable
|
||
closeOnClickOverlay
|
||
safeAreaInsetBottom
|
||
onClose={() => setShowDetail(false)}
|
||
>
|
||
{orderDetail && (
|
||
<View className='detail-popup'>
|
||
<Text className='detail-popup__title'>{orderDetail.order_no}</Text>
|
||
<View className='detail-popup__meta'>
|
||
<Text>{orderDetail.order_date} 下单 {orderDetail.created_at}</Text>
|
||
<Text className='detail-popup__status'>
|
||
{detailStatusName || ORDER_STATUS_TEXT[orderDetail.status] || ''}
|
||
</Text>
|
||
</View>
|
||
<ScrollView scrollY className='detail-popup__list'>
|
||
{(orderDetail.items ?? []).map(item => (
|
||
<View key={item.id} className='detail-popup__item'>
|
||
<View className='detail-popup__item-info'>
|
||
<Text className='detail-popup__item-name'>{item.product_name}</Text>
|
||
<Text className='detail-popup__item-spec'>
|
||
{item.product_spec ? `${item.product_spec} ` : ''}¥{item.price}/{item.unit} × {item.quantity}
|
||
</Text>
|
||
</View>
|
||
<Text className='detail-popup__item-amount'>¥{item.amount}</Text>
|
||
</View>
|
||
))}
|
||
</ScrollView>
|
||
{orderDetail.remark && (
|
||
<Text className='detail-popup__remark'>备注:{orderDetail.remark}</Text>
|
||
)}
|
||
<View className='detail-popup__footer'>
|
||
<Text className='detail-popup__total'>合计 ¥{orderDetail.total_amount}</Text>
|
||
</View>
|
||
</View>
|
||
)}
|
||
</Popup>
|
||
</View>
|
||
)
|
||
}
|