巴青农资商城

detail.vue 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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 class="detail-shop__avatar" :src="detail.shopAvatar" mode="aspectFill" />
  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 { mapOrderDetail, getItemReviewAction } from '@/utils/orderDisplay'
  101. import { ensureApiToken } from '@/utils/apiAuth'
  102. import { ORDER_ACTION } from '@/constants/order'
  103. import { runOrderAction, openPayModal } from '@/utils/orderAction'
  104. import { goReviewEdit, goReviewView } from '@/utils/orderNav'
  105. import OrderStatusBar from '@/components/order/OrderStatusBar.vue'
  106. import OrderGoodsRow from '@/components/order/OrderGoodsRow.vue'
  107. import { useActionGuard } from '@/utils/actionGuard'
  108. import OrderActionBar from '@/components/order/OrderActionBar.vue'
  109. const actionGuard = useActionGuard()
  110. const orderId = ref('')
  111. const detail = ref(null)
  112. const pageLoading = ref(true)
  113. const pageError = ref('')
  114. const actionLoading = actionGuard.locked
  115. const scrollHeight = ref('600px')
  116. const statusSubText = computed(() => {
  117. if (!detail.value) return ''
  118. if (detail.value.closeReason) return detail.value.closeReason
  119. if (detail.value.latestTrace && detail.value.latestTrace.content) {
  120. return detail.value.latestTrace.content
  121. }
  122. return ''
  123. })
  124. function calcScrollHeight() {
  125. try {
  126. const sys = uni.getSystemInfoSync()
  127. const hasFooter = detail.value && detail.value.actions && detail.value.actions.length
  128. scrollHeight.value = `${(sys.windowHeight || 600) - (hasFooter ? 72 : 0)}px`
  129. } catch (e) {
  130. scrollHeight.value = '600px'
  131. }
  132. }
  133. async function loadDetail() {
  134. if (!orderId.value) return
  135. pageLoading.value = !detail.value
  136. pageError.value = ''
  137. try {
  138. const res = await getOrderDetail(orderId.value)
  139. detail.value = mapOrderDetail(res.data)
  140. if (!detail.value) {
  141. throw new Error('订单数据异常')
  142. }
  143. calcScrollHeight()
  144. } catch (e) {
  145. pageError.value = (e && e.message) || '加载失败'
  146. if (!detail.value) detail.value = null
  147. } finally {
  148. pageLoading.value = false
  149. }
  150. }
  151. function onItemReview(item, code) {
  152. if (!detail.value) return
  153. actionGuard.run(() => {
  154. if (code === ORDER_ACTION.REVIEW) {
  155. goReviewEdit(detail.value.orderId, item.orderItemId)
  156. return
  157. }
  158. if (code === ORDER_ACTION.VIEW_REVIEW) {
  159. goReviewView(detail.value.orderId, item.orderItemId)
  160. }
  161. })
  162. }
  163. function onAction(code) {
  164. if (!detail.value) return
  165. actionGuard.run(async () => {
  166. if (code === ORDER_ACTION.PAY) {
  167. const result = await openPayModal(
  168. detail.value.orderId,
  169. detail.value.orderNo,
  170. detail.value.payAmountText
  171. )
  172. if (result && result.status) {
  173. await loadDetail()
  174. }
  175. return
  176. }
  177. await runOrderAction(code, {
  178. orderId: detail.value.orderId,
  179. firstItem: detail.value.items && detail.value.items[0],
  180. items: detail.value.items,
  181. onRefresh: loadDetail
  182. })
  183. })
  184. }
  185. onLoad((options) => {
  186. orderId.value = options.orderId || ''
  187. if (!orderId.value) {
  188. pageError.value = '订单信息缺失'
  189. pageLoading.value = false
  190. }
  191. })
  192. onShow(() => {
  193. if (!ensureApiToken(false)) return
  194. if (!orderId.value) return
  195. loadDetail()
  196. })
  197. </script>
  198. <style lang="scss" scoped>
  199. .order-detail-page {
  200. min-height: 100vh;
  201. background: #f5f6f8;
  202. padding-bottom: env(safe-area-inset-bottom);
  203. }
  204. .order-detail-state {
  205. padding: 120rpx 48rpx;
  206. display: flex;
  207. flex-direction: column;
  208. align-items: center;
  209. }
  210. .order-detail-scroll {
  211. box-sizing: border-box;
  212. }
  213. .detail-card {
  214. margin: 16rpx 24rpx;
  215. padding: 24rpx;
  216. background: #fff;
  217. border-radius: 12rpx;
  218. }
  219. .detail-card__title {
  220. display: block;
  221. margin-bottom: 16rpx;
  222. font-size: 28rpx;
  223. font-weight: 600;
  224. color: #333;
  225. }
  226. .detail-address__name {
  227. display: block;
  228. font-size: 28rpx;
  229. color: #333;
  230. }
  231. .detail-address__addr {
  232. display: block;
  233. margin-top: 8rpx;
  234. font-size: 26rpx;
  235. color: #666;
  236. line-height: 1.5;
  237. }
  238. .detail-shop {
  239. display: flex;
  240. align-items: center;
  241. margin-bottom: 8rpx;
  242. padding-bottom: 12rpx;
  243. border-bottom: 1rpx solid #f5f5f5;
  244. }
  245. .detail-shop__avatar {
  246. width: 44rpx;
  247. height: 44rpx;
  248. border-radius: 8rpx;
  249. background: #eee;
  250. }
  251. .detail-shop__name {
  252. margin-left: 12rpx;
  253. font-size: 28rpx;
  254. color: #333;
  255. font-weight: 500;
  256. }
  257. .detail-amount-row {
  258. display: flex;
  259. justify-content: space-between;
  260. padding: 10rpx 0;
  261. font-size: 28rpx;
  262. color: #666;
  263. }
  264. .detail-amount-row--total {
  265. margin-top: 8rpx;
  266. padding-top: 16rpx;
  267. border-top: 1rpx solid #f0f0f0;
  268. color: #333;
  269. font-weight: 500;
  270. }
  271. .detail-amount-row__price {
  272. color: #e53935;
  273. font-size: 32rpx;
  274. font-weight: 600;
  275. }
  276. .detail-info-row {
  277. display: flex;
  278. padding: 10rpx 0;
  279. font-size: 26rpx;
  280. }
  281. .detail-info-row__label {
  282. width: 160rpx;
  283. color: #999;
  284. flex-shrink: 0;
  285. }
  286. .detail-info-row__val {
  287. flex: 1;
  288. color: #333;
  289. word-break: break-all;
  290. }
  291. .detail-logistics__content {
  292. display: block;
  293. font-size: 28rpx;
  294. color: #333;
  295. line-height: 1.5;
  296. }
  297. .detail-logistics__time,
  298. .detail-logistics__no {
  299. display: block;
  300. margin-top: 8rpx;
  301. font-size: 24rpx;
  302. color: #999;
  303. }
  304. .order-detail-footer {
  305. position: fixed;
  306. left: 0;
  307. right: 0;
  308. bottom: 0;
  309. padding: 16rpx 24rpx;
  310. padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
  311. background: #fff;
  312. border-top: 1rpx solid #eee;
  313. z-index: 10;
  314. }
  315. </style>