巴青农资商城

detail.vue 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <template>
  2. <view class="order-detail-page">
  3. <view v-if="pageLoading" class="order-detail-state">
  4. <u-loading-icon mode="circle" />
  5. </view>
  6. <view v-else-if="pageError" class="order-detail-state">
  7. <u-empty mode="page" :text="pageError" icon-size="80" />
  8. <u-button type="primary" text="重试" size="small" custom-style="margin-top:24rpx" @click="loadDetail" />
  9. </view>
  10. <template v-else-if="detail">
  11. <scroll-view class="order-detail-scroll" scroll-y :style="{ height: scrollHeight }">
  12. <order-status-bar
  13. :status-text="detail.statusText"
  14. :pay-remain-seconds="detail.payRemainSeconds"
  15. :sub-text="statusSubText"
  16. />
  17. <view v-if="detail.latestTrace" class="detail-card detail-logistics">
  18. <text class="detail-card__title">物流信息</text>
  19. <text class="detail-logistics__content">{{ detail.latestTrace.content }}</text>
  20. <text class="detail-logistics__time">{{ detail.latestTrace.traceTime }}</text>
  21. <text v-if="detail.trackingNo" class="detail-logistics__no">
  22. {{ detail.logisticsCompany }} {{ detail.trackingNo }}
  23. </text>
  24. </view>
  25. <view class="detail-card">
  26. <text class="detail-card__title">收货信息</text>
  27. <text class="detail-address__name">{{ detail.consigneeName }} {{ detail.consigneeMobile }}</text>
  28. <text class="detail-address__addr">{{ detail.consigneeAddress }}</text>
  29. </view>
  30. <view class="detail-card">
  31. <view class="detail-shop">
  32. <image-preview class="detail-shop__avatar" :src="detail.shopAvatar" />
  33. <text class="detail-shop__name">{{ detail.shopName }}</text>
  34. </view>
  35. <order-goods-row
  36. v-for="item in detail.items"
  37. :key="item.orderItemId || item.goodsId"
  38. :item="item"
  39. show-subtotal
  40. :review-action="getItemReviewAction(item)"
  41. @review="(code) => onItemReview(item, code)"
  42. />
  43. </view>
  44. <view class="detail-card">
  45. <view class="detail-amount-row">
  46. <text>商品总价</text>
  47. <text>¥{{ detail.goodsAmountText }}</text>
  48. </view>
  49. <view class="detail-amount-row">
  50. <text>运费</text>
  51. <text>¥{{ detail.freightAmountText }}</text>
  52. </view>
  53. <view class="detail-amount-row detail-amount-row--total">
  54. <text>实付金额</text>
  55. <text class="detail-amount-row__price">¥{{ detail.payAmountText }}</text>
  56. </view>
  57. </view>
  58. <view class="detail-card">
  59. <text class="detail-card__title">订单信息</text>
  60. <view class="detail-info-row">
  61. <text class="detail-info-row__label">订单编号</text>
  62. <text class="detail-info-row__val">{{ detail.orderNo }}</text>
  63. </view>
  64. <view class="detail-info-row">
  65. <text class="detail-info-row__label">下单时间</text>
  66. <text class="detail-info-row__val">{{ detail.createTime }}</text>
  67. </view>
  68. <view v-if="detail.payTime" class="detail-info-row">
  69. <text class="detail-info-row__label">支付时间</text>
  70. <text class="detail-info-row__val">{{ detail.payTime }}</text>
  71. </view>
  72. <view v-if="detail.payTypeText" class="detail-info-row">
  73. <text class="detail-info-row__label">支付方式</text>
  74. <text class="detail-info-row__val">{{ detail.payTypeText }}</text>
  75. </view>
  76. <view v-if="detail.finishTime" class="detail-info-row">
  77. <text class="detail-info-row__label">成交时间</text>
  78. <text class="detail-info-row__val">{{ detail.finishTime }}</text>
  79. </view>
  80. <view v-if="detail.closeReason" class="detail-info-row">
  81. <text class="detail-info-row__label">关闭原因</text>
  82. <text class="detail-info-row__val">{{ detail.closeReason }}</text>
  83. </view>
  84. </view>
  85. </scroll-view>
  86. <view v-if="detail.actions.length" class="order-detail-footer">
  87. <order-action-bar
  88. :actions="detail.actions"
  89. :disabled="actionLoading"
  90. @action="onAction"
  91. />
  92. </view>
  93. </template>
  94. </view>
  95. </template>
  96. <script setup>
  97. import { ref, computed } from 'vue'
  98. import { onLoad, onShow } from '@dcloudio/uni-app'
  99. import { getOrderDetail } from '@/api/order'
  100. import {
  101. mapOrderDetail,
  102. getItemReviewAction,
  103. resolveOrderAftersaleStatus
  104. } from '@/utils/orderDisplay'
  105. import { ensureApiToken } from '@/utils/apiAuth'
  106. import { ORDER_ACTION } from '@/constants/order'
  107. import { runOrderAction, openPayModal } from '@/utils/orderAction'
  108. import { goReviewEdit, goReviewView } from '@/utils/orderNav'
  109. import OrderStatusBar from '@/components/order/OrderStatusBar.vue'
  110. import OrderGoodsRow from '@/components/order/OrderGoodsRow.vue'
  111. import { useActionGuard } from '@/utils/actionGuard'
  112. import OrderActionBar from '@/components/order/OrderActionBar.vue'
  113. const actionGuard = useActionGuard()
  114. const orderId = ref('')
  115. const routeAftersaleStatus = ref('')
  116. const detail = ref(null)
  117. const pageLoading = ref(true)
  118. const pageError = ref('')
  119. const actionLoading = actionGuard.locked
  120. const scrollHeight = ref('600px')
  121. const statusSubText = computed(() => {
  122. if (!detail.value) return ''
  123. if (detail.value.closeReason) return detail.value.closeReason
  124. if (detail.value.latestTrace && detail.value.latestTrace.content) {
  125. return detail.value.latestTrace.content
  126. }
  127. return ''
  128. })
  129. function calcScrollHeight() {
  130. try {
  131. const sys = uni.getSystemInfoSync()
  132. const hasFooter = detail.value && detail.value.actions && detail.value.actions.length
  133. scrollHeight.value = `${(sys.windowHeight || 600) - (hasFooter ? 72 : 0)}px`
  134. } catch (e) {
  135. scrollHeight.value = '600px'
  136. }
  137. }
  138. async function loadDetail() {
  139. if (!orderId.value) return
  140. pageLoading.value = !detail.value
  141. pageError.value = ''
  142. try {
  143. const res = await getOrderDetail(orderId.value)
  144. let aftersaleStatus = routeAftersaleStatus.value || null
  145. if (!aftersaleStatus) {
  146. aftersaleStatus = await resolveOrderAftersaleStatus(orderId.value)
  147. }
  148. detail.value = mapOrderDetail(res.data, { aftersaleStatus })
  149. if (!detail.value) {
  150. throw new Error('订单数据异常')
  151. }
  152. calcScrollHeight()
  153. } catch (e) {
  154. pageError.value = (e && e.message) || '加载失败'
  155. if (!detail.value) detail.value = null
  156. } finally {
  157. pageLoading.value = false
  158. }
  159. }
  160. function onItemReview(item, code) {
  161. if (!detail.value) return
  162. actionGuard.run(() => {
  163. if (code === ORDER_ACTION.REVIEW) {
  164. goReviewEdit(detail.value.orderId, item.orderItemId)
  165. return
  166. }
  167. if (code === ORDER_ACTION.VIEW_REVIEW) {
  168. goReviewView(detail.value.orderId, item.orderItemId)
  169. }
  170. })
  171. }
  172. function onAction(code) {
  173. if (!detail.value) return
  174. actionGuard.run(async () => {
  175. if (code === ORDER_ACTION.PAY) {
  176. const result = await openPayModal(
  177. detail.value.orderId,
  178. detail.value.orderNo,
  179. detail.value.payAmountText
  180. )
  181. if (result && result.status) {
  182. await loadDetail()
  183. }
  184. return
  185. }
  186. await runOrderAction(code, {
  187. orderId: detail.value.orderId,
  188. firstItem: detail.value.items && detail.value.items[0],
  189. items: detail.value.items,
  190. onRefresh: loadDetail
  191. })
  192. })
  193. }
  194. onLoad((options) => {
  195. orderId.value = options.orderId || ''
  196. routeAftersaleStatus.value = options.aftersaleStatus || ''
  197. if (!orderId.value) {
  198. pageError.value = '订单信息缺失'
  199. pageLoading.value = false
  200. }
  201. })
  202. onShow(() => {
  203. if (!ensureApiToken(false)) return
  204. if (!orderId.value) return
  205. loadDetail()
  206. })
  207. </script>
  208. <style lang="scss" scoped>
  209. .order-detail-page {
  210. min-height: 100vh;
  211. background: #f5f6f8;
  212. padding-bottom: env(safe-area-inset-bottom);
  213. }
  214. .order-detail-state {
  215. padding: 120rpx 48rpx;
  216. display: flex;
  217. flex-direction: column;
  218. align-items: center;
  219. }
  220. .order-detail-scroll {
  221. box-sizing: border-box;
  222. }
  223. .detail-card {
  224. margin: 16rpx 24rpx;
  225. padding: 24rpx;
  226. background: #fff;
  227. border-radius: 12rpx;
  228. }
  229. .detail-card__title {
  230. display: block;
  231. margin-bottom: 16rpx;
  232. font-size: 28rpx;
  233. font-weight: 600;
  234. color: #333;
  235. }
  236. .detail-address__name {
  237. display: block;
  238. font-size: 28rpx;
  239. color: #333;
  240. }
  241. .detail-address__addr {
  242. display: block;
  243. margin-top: 8rpx;
  244. font-size: 26rpx;
  245. color: #666;
  246. line-height: 1.5;
  247. }
  248. .detail-shop {
  249. display: flex;
  250. align-items: center;
  251. margin-bottom: 8rpx;
  252. padding-bottom: 12rpx;
  253. border-bottom: 1rpx solid #f5f5f5;
  254. }
  255. .detail-shop__avatar {
  256. width: 44rpx;
  257. height: 44rpx;
  258. border-radius: 8rpx;
  259. background: #eee;
  260. }
  261. .detail-shop__name {
  262. margin-left: 12rpx;
  263. font-size: 28rpx;
  264. color: #333;
  265. font-weight: 500;
  266. }
  267. .detail-amount-row {
  268. display: flex;
  269. justify-content: space-between;
  270. padding: 10rpx 0;
  271. font-size: 28rpx;
  272. color: #666;
  273. }
  274. .detail-amount-row--total {
  275. margin-top: 8rpx;
  276. padding-top: 16rpx;
  277. border-top: 1rpx solid #f0f0f0;
  278. color: #333;
  279. font-weight: 500;
  280. }
  281. .detail-amount-row__price {
  282. color: #e53935;
  283. font-size: 32rpx;
  284. font-weight: 600;
  285. }
  286. .detail-info-row {
  287. display: flex;
  288. padding: 10rpx 0;
  289. font-size: 26rpx;
  290. }
  291. .detail-info-row__label {
  292. width: 160rpx;
  293. color: #999;
  294. flex-shrink: 0;
  295. }
  296. .detail-info-row__val {
  297. flex: 1;
  298. color: #333;
  299. word-break: break-all;
  300. }
  301. .detail-logistics__content {
  302. display: block;
  303. font-size: 28rpx;
  304. color: #333;
  305. line-height: 1.5;
  306. }
  307. .detail-logistics__time,
  308. .detail-logistics__no {
  309. display: block;
  310. margin-top: 8rpx;
  311. font-size: 24rpx;
  312. color: #999;
  313. }
  314. .order-detail-footer {
  315. position: fixed;
  316. left: 0;
  317. right: 0;
  318. bottom: 0;
  319. padding: 16rpx 24rpx;
  320. padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
  321. background: #fff;
  322. border-top: 1rpx solid #eee;
  323. z-index: 10;
  324. }
  325. </style>