index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import { View, ScrollView, Text } from '@tarojs/components'
  2. import { useInfiniteQuery } from '@tanstack/react-query'
  3. import { useState } from 'react'
  4. import Taro from '@tarojs/taro'
  5. import { orderClient } from '@/api'
  6. import { InferResponseType } from 'hono'
  7. import { Navbar } from '@/components/ui/navbar'
  8. import { Card } from '@/components/ui/card'
  9. import { Button } from '@/components/ui/button'
  10. import { useAuth } from '@/utils/auth'
  11. type OrderResponse = InferResponseType<typeof orderClient.$get, 200>
  12. type Order = OrderResponse['data'][0]
  13. // 订单状态映射
  14. const orderStatusMap = {
  15. 0: { text: '待发货', color: 'text-orange-500' },
  16. 1: { text: '已发货', color: 'text-blue-500' },
  17. 2: { text: '已完成', color: 'text-green-500' },
  18. 3: { text: '已退货', color: 'text-red-500' }
  19. }
  20. // 支付状态映射
  21. const payStatusMap = {
  22. 0: { text: '未支付', color: 'text-red-500' },
  23. 1: { text: '支付中', color: 'text-yellow-500' },
  24. 2: { text: '已支付', color: 'text-green-500' },
  25. 3: { text: '已退款', color: 'text-gray-500' },
  26. 4: { text: '支付失败', color: 'text-red-500' },
  27. 5: { text: '已关闭', color: 'text-gray-500' }
  28. }
  29. export default function OrderListPage() {
  30. const { user } = useAuth()
  31. const [activeTab, setActiveTab] = useState('all')
  32. const {
  33. data,
  34. isLoading,
  35. isFetchingNextPage,
  36. fetchNextPage,
  37. hasNextPage,
  38. refetch
  39. } = useInfiniteQuery({
  40. queryKey: ['orders', user?.id, activeTab],
  41. queryFn: async ({ pageParam = 1 }) => {
  42. let filters: any = { userId: user?.id }
  43. // 根据标签筛选
  44. switch (activeTab) {
  45. case 'unpaid':
  46. filters.payState = 0
  47. break
  48. case 'unshipped':
  49. filters.payState = 2
  50. filters.state = 0
  51. break
  52. case 'shipped':
  53. filters.state = 1
  54. break
  55. case 'completed':
  56. filters.state = 2
  57. break
  58. }
  59. const response = await orderClient.$get({
  60. query: {
  61. page: pageParam,
  62. pageSize: 10,
  63. filters: JSON.stringify(filters),
  64. order: JSON.stringify({ createdAt: 'DESC' })
  65. }
  66. })
  67. if (response.status !== 200) {
  68. throw new Error('获取订单失败')
  69. }
  70. return response.json()
  71. },
  72. enabled: !!user?.id,
  73. getNextPageParam: (lastPage) => {
  74. const { pagination } = lastPage
  75. const totalPages = Math.ceil(pagination.total / pagination.pageSize)
  76. return pagination.current < totalPages ? pagination.current + 1 : undefined
  77. },
  78. staleTime: 5 * 60 * 1000,
  79. initialPageParam: 1,
  80. })
  81. // 合并所有分页数据
  82. const allOrders = data?.pages.flatMap(page => page.data) || []
  83. // 触底加载更多
  84. const handleScrollToLower = () => {
  85. if (hasNextPage && !isFetchingNextPage) {
  86. fetchNextPage()
  87. }
  88. }
  89. // 下拉刷新
  90. const onPullDownRefresh = () => {
  91. refetch().finally(() => {
  92. Taro.stopPullDownRefresh()
  93. })
  94. }
  95. // 查看订单详情
  96. const handleOrderDetail = (order: Order) => {
  97. Taro.navigateTo({
  98. url: `/pages/order-detail/index?id=${order.id}`
  99. })
  100. }
  101. // 解析商品详情
  102. const parseGoodsDetail = (goodsDetail: string | null) => {
  103. try {
  104. return goodsDetail ? JSON.parse(goodsDetail) : []
  105. } catch {
  106. return []
  107. }
  108. }
  109. // 计算订单商品数量
  110. const getOrderItemCount = (order: Order) => {
  111. const goods = parseGoodsDetail(order.goodsDetail)
  112. return goods.reduce((sum: number, item: any) => sum + (item.num || 0), 0)
  113. }
  114. const tabs = [
  115. { key: 'all', label: '全部' },
  116. { key: 'unpaid', label: '待付款' },
  117. { key: 'unshipped', label: '待发货' },
  118. { key: 'shipped', label: '待收货' },
  119. { key: 'completed', label: '已完成' }
  120. ]
  121. return (
  122. <View className="min-h-screen bg-gray-50">
  123. <Navbar
  124. title="我的订单"
  125. leftIcon="i-heroicons-chevron-left-20-solid"
  126. onClickLeft={() => Taro.navigateBack()}
  127. />
  128. <ScrollView
  129. className="h-screen pt-12"
  130. scrollY
  131. onScrollToLower={handleScrollToLower}
  132. refresherEnabled
  133. refresherTriggered={false}
  134. onRefresherRefresh={onPullDownRefresh}
  135. >
  136. {/* 标签栏 */}
  137. <View className="bg-white mb-4">
  138. <View className="flex">
  139. {tabs.map((tab) => (
  140. <View
  141. key={tab.key}
  142. className={`flex-1 py-3 text-center ${
  143. activeTab === tab.key
  144. ? 'text-blue-500 border-b-2 border-blue-500'
  145. : 'text-gray-600'
  146. }`}
  147. onClick={() => setActiveTab(tab.key)}
  148. >
  149. {tab.label}
  150. </View>
  151. ))}
  152. </View>
  153. </View>
  154. <View className="px-4">
  155. {isLoading ? (
  156. <View className="flex justify-center py-10">
  157. <View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
  158. </View>
  159. ) : (
  160. <>
  161. {allOrders.map((order) => {
  162. const goods = parseGoodsDetail(order.goodsDetail)
  163. const totalQuantity = getOrderItemCount(order)
  164. return (
  165. <Card key={order.id} className="mb-4">
  166. <View className="p-4">
  167. {/* 订单头部 */}
  168. <View className="flex justify-between items-center mb-3 pb-3 border-b border-gray-100">
  169. <Text className="text-sm text-gray-600">
  170. 订单号: {order.orderNo}
  171. </Text>
  172. <Text className={`text-sm font-medium ${payStatusMap[order.payState as keyof typeof payStatusMap].color}`}>
  173. {payStatusMap[order.payState as keyof typeof payStatusMap].text}
  174. </Text>
  175. </View>
  176. {/* 商品列表 */}
  177. <View className="mb-3">
  178. {goods.slice(0, 3).map((item: any, index: number) => (
  179. <View key={index} className="flex items-center py-2">
  180. <img
  181. src={item.image || ''}
  182. className="w-16 h-16 rounded-lg mr-3"
  183. mode="aspectFill"
  184. />
  185. <View className="flex-1">
  186. <Text className="text-sm font-medium line-clamp-2">
  187. {item.name}
  188. </Text>
  189. <Text className="text-sm text-gray-500">
  190. ¥{item.price.toFixed(2)} × {item.num}
  191. </Text>
  192. </View>
  193. </View>
  194. ))}
  195. {goods.length > 3 && (
  196. <Text className="text-sm text-gray-500 text-center mt-2">
  197. 共 {totalQuantity} 件商品
  198. </Text>
  199. )}
  200. </View>
  201. {/* 订单信息 */}
  202. <View className="border-t border-gray-100 pt-3">
  203. <View className="flex justify-between items-center mb-3">
  204. <Text className="text-sm text-gray-600">
  205. 实付款: ¥{order.payAmount.toFixed(2)}
  206. </Text>
  207. <Text className={`text-sm font-medium ${orderStatusMap[order.state as keyof typeof orderStatusMap].color}`}>
  208. {orderStatusMap[order.state as keyof typeof orderStatusMap].text}
  209. </Text>
  210. </View>
  211. {/* 操作按钮 */}
  212. <View className="flex justify-end space-x-2">
  213. <Button
  214. size="sm"
  215. variant="outline"
  216. onClick={() => handleOrderDetail(order)}
  217. >
  218. 查看详情
  219. </Button>
  220. {order.payState === 0 && (
  221. <Button
  222. size="sm"
  223. variant="primary"
  224. onClick={() => {
  225. // 跳转到支付页面
  226. Taro.navigateTo({
  227. url: `/pages/payment/index?orderId=${order.id}`
  228. })
  229. }}
  230. >
  231. 去支付
  232. </Button>
  233. )}
  234. {order.state === 2 && (
  235. <Button
  236. size="sm"
  237. variant="outline"
  238. onClick={() => {
  239. // 申请退款
  240. Taro.showModal({
  241. title: '确认收货',
  242. content: '确认已收到商品吗?',
  243. success: (res) => {
  244. if (res.confirm) {
  245. // 这里可以调用确认收货的API
  246. Taro.showToast({
  247. title: '已确认收货',
  248. icon: 'success'
  249. })
  250. }
  251. }
  252. })
  253. }}
  254. >
  255. 确认收货
  256. </Button>
  257. )}
  258. </View>
  259. </View>
  260. </View>
  261. </Card>
  262. )
  263. })}
  264. {isFetchingNextPage && (
  265. <View className="flex justify-center py-4">
  266. <View className="i-heroicons-arrow-path-20-solid animate-spin w-6 h-6 text-blue-500" />
  267. <Text className="ml-2 text-sm text-gray-500">加载更多...</Text>
  268. </View>
  269. )}
  270. {!hasNextPage && allOrders.length > 0 && (
  271. <View className="text-center py-4 text-sm text-gray-400">
  272. 没有更多了
  273. </View>
  274. )}
  275. {!isLoading && allOrders.length === 0 && (
  276. <View className="flex flex-col items-center py-20">
  277. <View className="i-heroicons-clipboard-document-list-20-solid w-16 h-16 text-gray-300 mb-4" />
  278. <Text className="text-gray-500 mb-4">暂无订单</Text>
  279. <Button
  280. onClick={() => Taro.navigateTo({ url: '/pages/goods-list/index' })}
  281. >
  282. 去购物
  283. </Button>
  284. </View>
  285. )}
  286. </>
  287. )}
  288. </View>
  289. </ScrollView>
  290. </View>
  291. )
  292. }