| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- <template>
- <view class="order-detail-page">
- <view v-if="pageLoading" class="order-detail-state">
- <u-loading-icon mode="circle" />
- </view>
- <view v-else-if="pageError" class="order-detail-state">
- <u-empty mode="page" :text="pageError" icon-size="80" />
- <u-button type="primary" text="重试" size="small" custom-style="margin-top:24rpx" @click="loadDetail" />
- </view>
- <template v-else-if="detail">
- <scroll-view class="order-detail-scroll" scroll-y :style="{ height: scrollHeight }">
- <order-status-bar
- :status-text="detail.statusText"
- :pay-remain-seconds="detail.payRemainSeconds"
- :sub-text="statusSubText"
- />
- <view v-if="detail.latestTrace" class="detail-card detail-logistics">
- <text class="detail-card__title">物流信息</text>
- <text class="detail-logistics__content">{{ detail.latestTrace.content }}</text>
- <text class="detail-logistics__time">{{ detail.latestTrace.traceTime }}</text>
- <text v-if="detail.trackingNo" class="detail-logistics__no">
- {{ detail.logisticsCompany }} {{ detail.trackingNo }}
- </text>
- </view>
- <view class="detail-card">
- <text class="detail-card__title">收货信息</text>
- <text class="detail-address__name">{{ detail.consigneeName }} {{ detail.consigneeMobile }}</text>
- <text class="detail-address__addr">{{ detail.consigneeAddress }}</text>
- </view>
- <view class="detail-card">
- <view class="detail-shop">
- <image class="detail-shop__avatar" :src="detail.shopAvatar" mode="aspectFill" />
- <text class="detail-shop__name">{{ detail.shopName }}</text>
- </view>
- <order-goods-row
- v-for="item in detail.items"
- :key="item.orderItemId || item.goodsId"
- :item="item"
- show-subtotal
- :review-action="getItemReviewAction(item)"
- @review="(code) => onItemReview(item, code)"
- />
- </view>
- <view class="detail-card">
- <view class="detail-amount-row">
- <text>商品总价</text>
- <text>¥{{ detail.goodsAmountText }}</text>
- </view>
- <view class="detail-amount-row">
- <text>运费</text>
- <text>¥{{ detail.freightAmountText }}</text>
- </view>
- <view class="detail-amount-row detail-amount-row--total">
- <text>实付金额</text>
- <text class="detail-amount-row__price">¥{{ detail.payAmountText }}</text>
- </view>
- </view>
- <view class="detail-card">
- <text class="detail-card__title">订单信息</text>
- <view class="detail-info-row">
- <text class="detail-info-row__label">订单编号</text>
- <text class="detail-info-row__val">{{ detail.orderNo }}</text>
- </view>
- <view class="detail-info-row">
- <text class="detail-info-row__label">下单时间</text>
- <text class="detail-info-row__val">{{ detail.createTime }}</text>
- </view>
- <view v-if="detail.payTime" class="detail-info-row">
- <text class="detail-info-row__label">支付时间</text>
- <text class="detail-info-row__val">{{ detail.payTime }}</text>
- </view>
- <view v-if="detail.payTypeText" class="detail-info-row">
- <text class="detail-info-row__label">支付方式</text>
- <text class="detail-info-row__val">{{ detail.payTypeText }}</text>
- </view>
- <view v-if="detail.finishTime" class="detail-info-row">
- <text class="detail-info-row__label">成交时间</text>
- <text class="detail-info-row__val">{{ detail.finishTime }}</text>
- </view>
- <view v-if="detail.closeReason" class="detail-info-row">
- <text class="detail-info-row__label">关闭原因</text>
- <text class="detail-info-row__val">{{ detail.closeReason }}</text>
- </view>
- </view>
- </scroll-view>
- <view v-if="detail.actions.length" class="order-detail-footer">
- <order-action-bar
- :actions="detail.actions"
- :disabled="actionLoading"
- @action="onAction"
- />
- </view>
- </template>
- </view>
- </template>
- <script setup>
- import { ref, computed } from 'vue'
- import { onLoad, onShow } from '@dcloudio/uni-app'
- import { getOrderDetail } from '@/api/order'
- import { mapOrderDetail, getItemReviewAction } from '@/utils/orderDisplay'
- import { ensureApiToken } from '@/utils/apiAuth'
- import { ORDER_ACTION } from '@/constants/order'
- import { runOrderAction, openPayModal } from '@/utils/orderAction'
- import { goReviewEdit, goReviewView } from '@/utils/orderNav'
- import OrderStatusBar from '@/components/order/OrderStatusBar.vue'
- import OrderGoodsRow from '@/components/order/OrderGoodsRow.vue'
- import { useActionGuard } from '@/utils/actionGuard'
- import OrderActionBar from '@/components/order/OrderActionBar.vue'
- const actionGuard = useActionGuard()
- const orderId = ref('')
- const detail = ref(null)
- const pageLoading = ref(true)
- const pageError = ref('')
- const actionLoading = actionGuard.locked
- const scrollHeight = ref('600px')
- const statusSubText = computed(() => {
- if (!detail.value) return ''
- if (detail.value.closeReason) return detail.value.closeReason
- if (detail.value.latestTrace && detail.value.latestTrace.content) {
- return detail.value.latestTrace.content
- }
- return ''
- })
- function calcScrollHeight() {
- try {
- const sys = uni.getSystemInfoSync()
- const hasFooter = detail.value && detail.value.actions && detail.value.actions.length
- scrollHeight.value = `${(sys.windowHeight || 600) - (hasFooter ? 72 : 0)}px`
- } catch (e) {
- scrollHeight.value = '600px'
- }
- }
- async function loadDetail() {
- if (!orderId.value) return
- pageLoading.value = !detail.value
- pageError.value = ''
- try {
- const res = await getOrderDetail(orderId.value)
- detail.value = mapOrderDetail(res.data)
- if (!detail.value) {
- throw new Error('订单数据异常')
- }
- calcScrollHeight()
- } catch (e) {
- pageError.value = (e && e.message) || '加载失败'
- if (!detail.value) detail.value = null
- } finally {
- pageLoading.value = false
- }
- }
- function onItemReview(item, code) {
- if (!detail.value) return
- actionGuard.run(() => {
- if (code === ORDER_ACTION.REVIEW) {
- goReviewEdit(detail.value.orderId, item.orderItemId)
- return
- }
- if (code === ORDER_ACTION.VIEW_REVIEW) {
- goReviewView(detail.value.orderId, item.orderItemId)
- }
- })
- }
- function onAction(code) {
- if (!detail.value) return
- actionGuard.run(async () => {
- if (code === ORDER_ACTION.PAY) {
- const result = await openPayModal(
- detail.value.orderId,
- detail.value.orderNo,
- detail.value.payAmountText
- )
- if (result && result.status) {
- await loadDetail()
- }
- return
- }
- await runOrderAction(code, {
- orderId: detail.value.orderId,
- firstItem: detail.value.items && detail.value.items[0],
- items: detail.value.items,
- onRefresh: loadDetail
- })
- })
- }
- onLoad((options) => {
- orderId.value = options.orderId || ''
- if (!orderId.value) {
- pageError.value = '订单信息缺失'
- pageLoading.value = false
- }
- })
- onShow(() => {
- if (!ensureApiToken(false)) return
- if (!orderId.value) return
- loadDetail()
- })
- </script>
- <style lang="scss" scoped>
- .order-detail-page {
- min-height: 100vh;
- background: #f5f6f8;
- padding-bottom: env(safe-area-inset-bottom);
- }
- .order-detail-state {
- padding: 120rpx 48rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .order-detail-scroll {
- box-sizing: border-box;
- }
- .detail-card {
- margin: 16rpx 24rpx;
- padding: 24rpx;
- background: #fff;
- border-radius: 12rpx;
- }
- .detail-card__title {
- display: block;
- margin-bottom: 16rpx;
- font-size: 28rpx;
- font-weight: 600;
- color: #333;
- }
- .detail-address__name {
- display: block;
- font-size: 28rpx;
- color: #333;
- }
- .detail-address__addr {
- display: block;
- margin-top: 8rpx;
- font-size: 26rpx;
- color: #666;
- line-height: 1.5;
- }
- .detail-shop {
- display: flex;
- align-items: center;
- margin-bottom: 8rpx;
- padding-bottom: 12rpx;
- border-bottom: 1rpx solid #f5f5f5;
- }
- .detail-shop__avatar {
- width: 44rpx;
- height: 44rpx;
- border-radius: 8rpx;
- background: #eee;
- }
- .detail-shop__name {
- margin-left: 12rpx;
- font-size: 28rpx;
- color: #333;
- font-weight: 500;
- }
- .detail-amount-row {
- display: flex;
- justify-content: space-between;
- padding: 10rpx 0;
- font-size: 28rpx;
- color: #666;
- }
- .detail-amount-row--total {
- margin-top: 8rpx;
- padding-top: 16rpx;
- border-top: 1rpx solid #f0f0f0;
- color: #333;
- font-weight: 500;
- }
- .detail-amount-row__price {
- color: #e53935;
- font-size: 32rpx;
- font-weight: 600;
- }
- .detail-info-row {
- display: flex;
- padding: 10rpx 0;
- font-size: 26rpx;
- }
- .detail-info-row__label {
- width: 160rpx;
- color: #999;
- flex-shrink: 0;
- }
- .detail-info-row__val {
- flex: 1;
- color: #333;
- word-break: break-all;
- }
- .detail-logistics__content {
- display: block;
- font-size: 28rpx;
- color: #333;
- line-height: 1.5;
- }
- .detail-logistics__time,
- .detail-logistics__no {
- display: block;
- margin-top: 8rpx;
- font-size: 24rpx;
- color: #999;
- }
- .order-detail-footer {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- padding: 16rpx 24rpx;
- padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
- background: #fff;
- border-top: 1rpx solid #eee;
- z-index: 10;
- }
- </style>
|