Files
hfb_jysh/frontend/src/features/mobile/MobileOrdersPage.tsx
T

158 lines
5.2 KiB
TypeScript

import { Button, Empty, ErrorBlock, PullToRefresh, Skeleton, Tag } from 'antd-mobile'
import { RightOutline } from 'antd-mobile-icons'
import { Link, useNavigate } from 'react-router-dom'
import { BrandLogo } from '@/components/BrandLogo'
import { useMyOrders } from '@/features/orders/api'
import { orderStatusMeta, roleLabel } from '@/features/orders/labels'
import type { UserOrderListItem } from '@/features/orders/types'
import { useSessionStore } from '@/stores/session'
import { formatDateTime, formatMoney, formatText } from '@/shared/utils/format'
function orderSummary(item: UserOrderListItem) {
return item.rental_days ? `${item.rental_days} 天` : formatText(item.login_platform)
}
function MobileOrderCard({ item }: { item: UserOrderListItem }) {
const navigate = useNavigate()
const status = orderStatusMeta(item.status)
return (
<button
type="button"
className="mobile-order-card"
onClick={() => navigate(`/m/orders/${item.id}`)}
>
<div className="mobile-order-card__top">
<span className="mobile-order-card__no">{item.order_no}</span>
<Tag color={status.tone} fill="outline">
{status.label}
</Tag>
</div>
<div className="mobile-order-card__title">{item.title}</div>
<div className="mobile-order-card__meta">
<span>{roleLabel(item.role)}</span>
<span>{formatText(item.server_region)}</span>
<span>{orderSummary(item)}</span>
</div>
<div className="mobile-order-card__bottom">
<div>
<strong>{formatMoney(item.display_amount_cent)}</strong>
<small>押金 {formatMoney(item.deposit_amount_cent)}</small>
</div>
<span className="mobile-order-card__date">
{formatDateTime(item.created_at)} <RightOutline />
</span>
</div>
</button>
)
}
export function MobileOrdersPage() {
const navigate = useNavigate()
const token = useSessionStore(state => state.token)
const hydrated = useSessionStore(state => state.hydrated)
const query = useMyOrders(Boolean(token))
const items = query.data?.pages.flatMap(page => page.items) ?? []
const total = query.data?.pages[0]?.total ?? 0
if (!hydrated) {
return (
<div className="mobile-orders">
<Skeleton.Title animated />
<Skeleton.Paragraph lineCount={6} animated />
</div>
)
}
if (!token) {
return (
<div className="mobile-orders">
<section className="mobile-orders__head">
<span className="mobile-orders__eyebrow">MY ORDERS</span>
<h1>我的订单</h1>
<p>登录后查看租用进度、交接与结账记录。</p>
</section>
<div className="mobile-orders__login">
<BrandLogo height={40} variant="seal" />
<h2>先登录,再看订单</h2>
<p>订单、押金与结账明细只对交易双方可见。</p>
<Button
block
color="primary"
onClick={() => navigate('/m/login?redirect=/m/orders')}
>
去登录
</Button>
</div>
</div>
)
}
return (
<div className="mobile-orders">
<section className="mobile-orders__head">
<span className="mobile-orders__eyebrow">MY ORDERS</span>
<h1>我的订单</h1>
<p>{query.data ? `共 ${total} 单 · 下拉可刷新` : '正在读取你的订单…'}</p>
</section>
{query.isPending ? (
<div className="mobile-orders__skeleton">
{Array.from({ length: 3 }).map((_, index) => (
<div className="mobile-order-card mobile-order-card--skeleton" key={index}>
<Skeleton.Title animated />
<Skeleton.Paragraph lineCount={3} animated />
</div>
))}
</div>
) : query.isError ? (
<ErrorBlock
status="disconnected"
title="订单没有加载出来"
description="检查网络后重试,或重新登录。"
>
<Button color="primary" size="small" onClick={() => void query.refetch()}>
重新加载
</Button>
</ErrorBlock>
) : (
<PullToRefresh onRefresh={() => query.refetch().then(() => undefined)}>
<div className="mobile-orders__list">
{items.length === 0 ? (
<div className="mobile-orders__empty">
<Empty description="还没有订单" />
<Link to="/m" className="mobile-orders__empty-link">
去逛逛商品 <RightOutline />
</Link>
</div>
) : (
items.map(item => <MobileOrderCard item={item} key={item.id} />)
)}
{items.length > 0 ? (
<div className="mobile-orders__more">
{query.hasNextPage ? (
<Button
block
fill="outline"
loading={query.isFetchingNextPage}
onClick={() => void query.fetchNextPage()}
>
加载更多
</Button>
) : (
<span>已经看到全部订单</span>
)}
</div>
) : null}
</div>
</PullToRefresh>
)}
</div>
)
}