index.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. import { View, Text } from '@tarojs/components'
  2. import Taro from '@tarojs/taro'
  3. import { useMutation, useQueryClient } from '@tanstack/react-query'
  4. import { InferResponseType } from 'hono'
  5. import { orderClient } from '@/api'
  6. import { useState } from 'react'
  7. import CancelReasonDialog from '@/components/common/CancelReasonDialog'
  8. type OrderResponse = InferResponseType<typeof orderClient.$get, 200>
  9. type Order = OrderResponse['data'][0]
  10. interface OrderButtonBarProps {
  11. order: Order
  12. onViewDetail: (order: Order) => void
  13. onCancelOrder?: () => void
  14. }
  15. interface ActionButton {
  16. text: string
  17. type: 'primary' | 'outline'
  18. onClick: () => void
  19. }
  20. export default function OrderButtonBar({ order, onViewDetail, onCancelOrder }: OrderButtonBarProps) {
  21. const queryClient = useQueryClient()
  22. const [showCancelDialog, setShowCancelDialog] = useState(false)
  23. // 取消订单mutation
  24. const cancelOrderMutation = useMutation({
  25. mutationFn: async ({ orderId, reason }: { orderId: number; reason: string }) => {
  26. const response = await orderClient.cancelOrder.$post({
  27. json: {
  28. orderId,
  29. reason
  30. }
  31. })
  32. if (response.status !== 200) {
  33. throw new Error('取消订单失败')
  34. }
  35. return response.json()
  36. },
  37. onSuccess: (data) => {
  38. // 取消成功后刷新订单列表数据
  39. queryClient.invalidateQueries({ queryKey: ['orders'] })
  40. queryClient.invalidateQueries({ queryKey: ['order', order.id] })
  41. // 显示取消成功信息
  42. Taro.showToast({
  43. title: '订单取消成功',
  44. icon: 'success',
  45. duration: 2000
  46. })
  47. // 如果订单已支付,显示退款流程信息
  48. if (order.payState === 2) {
  49. setTimeout(() => {
  50. Taro.showModal({
  51. title: '退款处理中',
  52. content: '您的退款申请已提交,退款金额将在1-3个工作日内原路退回。',
  53. showCancel: false,
  54. confirmText: '知道了'
  55. })
  56. }, 1500)
  57. }
  58. },
  59. onError: (error) => {
  60. // 根据错误消息类型显示不同的用户友好提示
  61. let errorMessage = '取消失败,请稍后重试'
  62. if (error.message.includes('订单不存在')) {
  63. errorMessage = '订单不存在或已被删除'
  64. } else if (error.message.includes('订单状态不允许取消')) {
  65. errorMessage = '当前订单状态不允许取消'
  66. } else if (error.message.includes('网络')) {
  67. errorMessage = '网络连接失败,请检查网络后重试'
  68. }
  69. Taro.showToast({
  70. title: errorMessage,
  71. icon: 'error',
  72. duration: 3000
  73. })
  74. }
  75. })
  76. // 处理取消原因确认
  77. const handleCancelReasonConfirm = (reason: string) => {
  78. // 显示确认对话框
  79. Taro.showModal({
  80. title: '确认取消',
  81. content: `确定要取消订单吗?\n取消原因:${reason}`,
  82. success: (confirmRes) => {
  83. if (confirmRes.confirm) {
  84. // 调用取消订单API
  85. cancelOrderMutation.mutate({
  86. orderId: order.id,
  87. reason
  88. })
  89. }
  90. }
  91. })
  92. }
  93. // 取消订单
  94. const handleCancelOrder = () => {
  95. // 检查网络连接
  96. Taro.getNetworkType({
  97. success: (res) => {
  98. if (res.networkType === 'none') {
  99. Taro.showToast({
  100. title: '网络连接失败,请检查网络后重试',
  101. icon: 'error',
  102. duration: 3000
  103. })
  104. return
  105. }
  106. if (onCancelOrder) {
  107. // 使用外部提供的取消订单处理函数
  108. onCancelOrder()
  109. } else {
  110. // 使用组件内部的取消订单处理
  111. setShowCancelDialog(true)
  112. }
  113. },
  114. fail: () => {
  115. Taro.showToast({
  116. title: '网络状态检查失败',
  117. icon: 'error',
  118. duration: 3000
  119. })
  120. }
  121. })
  122. }
  123. // 去支付
  124. const handlePayOrder = () => {
  125. Taro.navigateTo({
  126. url: `/pages/payment/index?orderId=${order.id}&amount=${order.payAmount}`
  127. })
  128. }
  129. // 确认收货
  130. const handleConfirmReceipt = () => {
  131. Taro.showModal({
  132. title: '确认收货',
  133. content: '确认已收到商品吗?',
  134. success: async (res) => {
  135. if (res.confirm) {
  136. try {
  137. // 这里调用确认收货的API
  138. Taro.showToast({
  139. title: '已确认收货',
  140. icon: 'success'
  141. })
  142. } catch (error) {
  143. Taro.showToast({
  144. title: '确认失败',
  145. icon: 'error'
  146. })
  147. }
  148. }
  149. }
  150. })
  151. }
  152. // 申请退款
  153. const handleApplyRefund = () => {
  154. Taro.showModal({
  155. title: '申请退款',
  156. content: '确定要申请退款吗?',
  157. success: async (res) => {
  158. if (res.confirm) {
  159. try {
  160. // 这里调用申请退款的API
  161. Taro.showToast({
  162. title: '退款申请已提交',
  163. icon: 'success'
  164. })
  165. } catch (error) {
  166. Taro.showToast({
  167. title: '申请失败',
  168. icon: 'error'
  169. })
  170. }
  171. }
  172. }
  173. })
  174. }
  175. // 查看物流
  176. const handleViewLogistics = () => {
  177. Taro.showToast({
  178. title: '物流信息开发中',
  179. icon: 'none'
  180. })
  181. }
  182. // 根据订单状态显示不同的操作按钮
  183. const getActionButtons = (order: Order): ActionButton[] => {
  184. const buttons: ActionButton[] = []
  185. // 查看详情按钮 - 所有状态都显示
  186. buttons.push({
  187. text: '查看详情',
  188. type: 'outline',
  189. onClick: () => onViewDetail(order)
  190. })
  191. // 根据支付状态和订单状态显示不同的操作按钮
  192. if (order.payState === 0) {
  193. // 未支付
  194. buttons.push({
  195. text: '去支付',
  196. type: 'primary',
  197. onClick: handlePayOrder
  198. })
  199. buttons.push({
  200. text: '取消订单',
  201. type: 'outline',
  202. onClick: handleCancelOrder
  203. })
  204. } else if (order.payState === 2) {
  205. // 已支付
  206. if (order.state === 0) {
  207. // 待发货
  208. buttons.push({
  209. text: '申请退款',
  210. type: 'outline',
  211. onClick: handleApplyRefund
  212. })
  213. } else if (order.state === 1) {
  214. // 已发货
  215. buttons.push({
  216. text: '确认收货',
  217. type: 'primary',
  218. onClick: handleConfirmReceipt
  219. })
  220. buttons.push({
  221. text: '查看物流',
  222. type: 'outline',
  223. onClick: handleViewLogistics
  224. })
  225. } else if (order.state === 2) {
  226. // 已完成
  227. buttons.push({
  228. text: '申请售后',
  229. type: 'outline',
  230. onClick: handleApplyRefund
  231. })
  232. }
  233. }
  234. return buttons
  235. }
  236. const actionButtons = getActionButtons(order)
  237. return (
  238. <>
  239. <View className="flex justify-end space-x-2">
  240. {actionButtons.map((button, index) => (
  241. <View
  242. key={index}
  243. className={`px-4 py-2 rounded-full text-sm font-medium border ${
  244. button.type === 'primary'
  245. ? 'bg-primary text-white border-primary'
  246. : 'bg-white text-gray-600 border-gray-300'
  247. } ${cancelOrderMutation.isPending && button.text === '取消订单' ? 'opacity-50' : ''}`}
  248. onClick={cancelOrderMutation.isPending && button.text === '取消订单' ? undefined : button.onClick}
  249. data-testid={button.text === '取消订单' ? 'cancel-order-button' : undefined}
  250. >
  251. <Text>
  252. {cancelOrderMutation.isPending && button.text === '取消订单' ? '取消中...' : button.text}
  253. </Text>
  254. </View>
  255. ))}
  256. </View>
  257. <CancelReasonDialog
  258. open={showCancelDialog}
  259. onOpenChange={setShowCancelDialog}
  260. onConfirm={handleCancelReasonConfirm}
  261. loading={cancelOrderMutation.isPending}
  262. />
  263. </>
  264. )
  265. }