Files
uni-fans-score/components/OrderItemCard.vue
T
2025-11-08 16:00:44 +08:00

384 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="order-item" >
<!-- 订单头部信息 -->
<view class="order-header">
<view class="header-left">
<view class="status-chip" :class="statusChipClass"><text class="chip-text">{{ statusText }}</text></view>
<view class="title">{{ titleText }}</view>
</view>
<view class="header-right">
<!-- 支付方式标识移到头部右侧 -->
<view class="payment-badge wx-score" v-if="order.payWay == 'wx_score_pay'">
<image src="/static/images/wxpayflag.png" mode="aspectFit" class="badge-icon"></image>
<view class="badge-text">
<text>{{ $t('order.wxPayScore') }}</text>
<text class="divider">|</text>
<text class="highlight">{{ $t('order.depositFree') }}</text>
</view>
</view>
<view class="payment-badge whitelist" v-else-if="order.payWay == 'wx_global_pay'">
<text class="badge-text">{{ $t('order.whitelistOrder') }}</text>
</view>
<view class="payment-badge member" v-else-if="order.payWay == 'wx_member_pay'">
<text class="badge-text">{{ $t('order.memberOrder') }}</text>
</view>
<view class="payment-badge deposit" v-else>
<text class="badge-text">{{ $t('order.wxPay') }}</text>
<text class="divider">|</text>
<text class="badge-text">{{ $t('order.depositPay') }}</text>
</view>
</view>
</view>
<!-- 订单内容 -->
<view class="order-body" @click="onDetails">
<!-- <view class="device-info">
<view class="device-left">
<view class="device-name">{{ titleText }}</view>
<view class="device-id">设备号{{ order.deviceId }}</view>
</view>
</view> -->
<!-- 订单时间信息 -->
<view class="order-times">
<view class="time-row">
<text class="time-label">{{ $t('order.rentLocation') }}</text>
<text class="time-value">{{ order.deviceName || order.positionName }}</text>
</view>
<view class="time-row">
<text class="time-label">{{ $t('order.rentTime') }}</text>
<text class="time-value">{{ order.startTime }}</text>
</view>
<view class="arrow" @click="onDetails">
<uv-icon name="arrow-right" size="24rpx" color="#999"></uv-icon>
</view>
<!-- <view class="time-row">
<text class="time-label">结束时间</text>
<text class="time-value">{{ order.endTime || '-' }}</text>
</view> -->
</view>
</view>
<!-- 订单底部 -->
<view class="order-footer">
<view class="footer-left">
<view v-if="isInUse" class="renting">
<image src="/static/order_time.png" mode="aspectFit" class="icon-time"></image>
{{ $t('order.renting') }}
</view>
<view v-else-if="isFinished" class="meta">
<view class="meta-item">
<image src="/static/order_time.png" mode="aspectFit" class="icon-time"></image>
{{ usedDurationText }}
</view>
<view class="meta-item">
<image src="/static/order_price.png" mode="aspectFit" class="icon-price"></image>
{{ displayAmount }}
</view>
</view>
<view v-else-if="isCancelled" class="cancelled">
{{ $t('order.orderCancelled') }}
</view>
</view>
<view class="actions">
<!-- 待支付状态显示支付和取消按钮 -->
<view v-if="isWaitingForPayment" class="action-item primary" @click="onPay">{{ $t('order.payNow') }}</view>
<view v-if="isWaitingForPayment" class="action-item secondary" @click="onCancel">{{ $t('order.cancelOrder') }}</view>
<!-- 使用中状态显示归还设备按钮 -->
<view v-if="isInUse" class="action-item primary" @click="onReturn">{{ $t('order.quickReturn') }}</view>
<!-- 查看详情按钮对所有订单都显示 -->
<!-- <view class="action-item secondary" >{{ $t('order.viewDetails') }}</view> -->
</view>
</view>
</view>
</template>
<script setup>
import { computed } from 'vue';
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const props = defineProps({
order: { type: Object, required: true },
orderStatusMap: { type: Object, required: true }
});
const emit = defineEmits(['pay', 'cancel', 'return-device', 'details']);
const rawStatus = computed(() => props.order.orderStatus ?? props.order.status);
const normalizedStatus = computed(() => {
const s = rawStatus.value;
switch (s) {
case 0:
case '0':
return 'waiting_for_payment';
case 1:
case '1':
return 'in_used';
case 2:
case '2':
return 'used_done';
case 3:
case '3':
return 'order_cancelled';
default:
return s || '';
}
});
const statusDef = computed(() => props.orderStatusMap?.[rawStatus.value] || props.orderStatusMap?.[normalizedStatus.value] || {});
const statusText = computed(() => statusDef.value.text || '');
const statusChipClass = computed(() => {
const cls = statusDef.value.class || '';
if (cls.includes('status-using')) return 'chip-using';
if (cls.includes('status-waiting')) return 'chip-waiting';
if (cls.includes('status-finished')) return 'chip-finished';
if (cls.includes('status-cancelled')) return 'chip-cancelled';
return 'chip-default';
});
const isWaitingForPayment = computed(() => normalizedStatus.value === 'waiting_for_payment');
const isInUse = computed(() => normalizedStatus.value === 'in_used');
const isFinished = computed(() => normalizedStatus.value === 'used_done');
const isCancelled = computed(() => normalizedStatus.value === 'order_cancelled');
const titleText = computed(() => $t('order.rentFan'));
// 显示金额(优先后端给定字段)
const displayAmount = computed(() => props.order.amount || props.order.payAmount || props.order.actualDeviceAmount || props.order.currentFee || '0.00');
// 使用时长
const usedDurationText = computed(() => {
const start = parseDate(props.order.startTime);
const end = parseDate(props.order.endTime) || new Date();
if (!start) return '';
const diffMs = Math.max(0, end.getTime() - start.getTime());
const minutes = Math.floor(diffMs / 60000);
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
if (hours > 0) return `${hours}${$t('time.hour')}${mins}${$t('time.minute')}`;
return `${mins}${$t('time.minute')}`;
});
function parseDate(str) {
if (!str) return null;
try {
return new Date(String(str).replace(/-/g, '/'));
} catch (e) {
return null;
}
}
const onPay = () => emit('pay', props.order);
const onCancel = () => emit('cancel', props.order);
const onReturn = () => emit('return-device', props.order);
const onDetails = () => emit('details', props.order);
</script>
<style lang="scss" scoped>
.order-item {
background: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
// 订单头部
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
// border-bottom: 1rpx solid #f0f0f0;
.header-left { display: flex; align-items: center; }
.title { font-size: 30rpx; color: #222; margin-left: 16rpx; font-weight: 600; }
.status-chip {
padding: 8rpx 20rpx;
border-radius: 8rpx;
font-size: 24rpx;
background: #f5f5f5;
transform: skewX(-15deg);
overflow: hidden;
.chip-text { display: inline-block; transform: skewX(15deg); }
&.chip-using { background: rgba(7,193,96,0.12); color: #07c160; }
&.chip-waiting { background: rgba(255,152,0,0.12); color: #FF9800; }
&.chip-finished { background: rgba(76,175,80,0.12); color: #4CAF50; }
&.chip-cancelled { background: rgba(158,158,158,0.12); color: #9E9E9E; }
}
.header-right { display: flex; align-items: center; }
// 支付标识:仅文字与图标,无底色
.payment-badge {
display: inline-flex;
align-items: center;
padding: 0;
border-radius: 0;
white-space: nowrap;
.badge-icon { width: 32rpx; height: 26rpx; margin-right: 8rpx; }
.badge-text { font-size: 22rpx; color: #07c160; font-weight: 500; background: transparent; }
.divider { margin: 0 6rpx; color: #07c160; }
}
// 不同支付方式的文字颜色
.payment-badge.whitelist {
.badge-text, .divider { color: #FF9800; }
}
.payment-badge.member {
.badge-text, .divider { color: #1976D2; }
}
.payment-badge.deposit {
.badge-text, .divider { color: #666; }
}
}
// 订单内容
.order-body {
padding: 24rpx;
.device-info {
margin-bottom: 20rpx;
display: flex;
justify-content: space-between;
align-items: flex-start;
.device-left {
flex: 1;
margin-right: 20rpx;
.device-name {
font-size: 32rpx;
font-weight: 500;
color: #333;
margin-bottom: 6rpx;
}
.device-id {
font-size: 26rpx;
color: #999;
margin-bottom: 0;
}
}
.device-right {
// 支付分标识
.payment-badge {
display: inline-flex;
align-items: center;
padding: 6rpx 12rpx;
border-radius: 8rpx;
white-space: nowrap;
&.wx-score {
background: rgba(7, 193, 96, 0.08);
.badge-icon {
width: 32rpx;
height: 26rpx;
margin-right: 8rpx;
}
.badge-text {
font-size: 22rpx;
color: #07c160;
display: flex;
align-items: center;
.divider { margin: 0 6rpx; }
.highlight { font-weight: 500; }
}
}
&.member {
background: rgba(25, 118, 210, 0.08);
.badge-text {
font-size: 22rpx;
color: #1976D2;
font-weight: 500;
}
}
&.deposit {
background: #f5f5f5;
.badge-text {
font-size: 22rpx;
color: #666;
font-weight: 500;
}
}
}
}
}
.order-times {
position: relative;
.time-row {
display: flex;
font-size: 26rpx;
margin-bottom: 8rpx;
.time-label { color: #999; width: 140rpx; }
.time-value { color: #333; flex: 1; }
}
.arrow {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 40rpx;
display: flex;
align-items: center;
justify-content: center;
color: #999;
}
}
}
// 订单底部
.order-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 24rpx;
background: #fafafa;
border-top: 1rpx solid #f0f0f0;
.footer-left { display: flex; align-items: center; }
.renting { font-size: 26rpx; color: #333; display: flex; align-items: center; }
.meta { display: flex; align-items: center; }
.meta-item { font-size: 26rpx; color: #333; display: flex; align-items: center; margin-right: 28rpx; }
.cancelled { font-size: 26rpx; color: #999; }
.icon-time { width: 32rpx; height: 32rpx; margin-right: 8rpx; }
.icon-price { width: 32rpx; height: 32rpx; margin-right: 8rpx; }
.actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
.action-item {
font-size: 26rpx;
padding: 10rpx 30rpx;
border-radius: 30rpx;
margin-left: 20rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10rpx;
&.primary { background: #07c160; color: #fff; }
&.secondary { background: #f5f5f5; color: #666; border: 1rpx solid #e0e0e0; }
&:active { opacity: 0.8; }
}
}
}
}
</style>