You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
456 lines
10 KiB
456 lines
10 KiB
<!--订单列表内容组件-->
|
|
<template>
|
|
<view class="order-list-card">
|
|
<!-- 操作按钮区域 -->
|
|
<view class="action-header" v-if="orderList && orderList.length > 0">
|
|
<view class="add-order-btn" @click.stop="handleAddOrder">
|
|
<text class="add-icon">+</text>
|
|
<text class="add-text">新增订单</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 订单列表 -->
|
|
<scroll-view
|
|
class="order-list"
|
|
v-if="orderList && orderList.length > 0"
|
|
scroll-y="true"
|
|
:enhanced="true"
|
|
style="height: 70vh;"
|
|
>
|
|
<view
|
|
class="order-item"
|
|
v-for="(order, index) in orderList"
|
|
:key="order && order.id ? order.id : `order-${index}`"
|
|
:class="{ 'pending-payment': order && order.status === 'pending' }"
|
|
@click.stop="order ? handleOrderClick(order) : null"
|
|
v-if="order"
|
|
>
|
|
<view class="order-header">
|
|
<view class="order-info">
|
|
<view class="order-no">订单号:{{ order.order_no || 'N/A' }}</view>
|
|
<view class="order-time" v-if="order.create_time">
|
|
{{ formatTime(order.create_time) }}
|
|
</view>
|
|
</view>
|
|
<view :class="['order-status',getStatusClass(order && order.status)]">
|
|
{{ getStatusText(order && order.status) }}
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 订单内容 -->
|
|
<view class="order-content">
|
|
<view class="product-info" v-if="order.product_name">
|
|
<view class="product-name">{{ order.product_name }}</view>
|
|
<view class="product-specs" v-if="order.product_specs">
|
|
{{ order.product_specs }}
|
|
</view>
|
|
</view>
|
|
|
|
<view class="order-details">
|
|
<!-- 价格信息 -->
|
|
<view class="detail-row" v-if="order.total_amount">
|
|
<text class="detail-label">订单金额:</text>
|
|
<text class="detail-value price">¥{{ order.total_amount }}</text>
|
|
</view>
|
|
|
|
<view class="detail-row" v-if="order.paid_amount !== undefined">
|
|
<text class="detail-label">已付金额:</text>
|
|
<text class="detail-value paid">¥{{ order.paid_amount }}</text>
|
|
</view>
|
|
|
|
<view class="detail-row" v-if="order.unpaid_amount !== undefined">
|
|
<text class="detail-label">未付金额:</text>
|
|
<text class="detail-value unpaid">¥{{ order.unpaid_amount }}</text>
|
|
</view>
|
|
|
|
<!-- 其他信息 -->
|
|
<view class="detail-row" v-if="order.payment_method">
|
|
<text class="detail-label">支付方式:</text>
|
|
<text class="detail-value">{{ order.payment_method }}</text>
|
|
</view>
|
|
|
|
<view class="detail-row" v-if="order.salesperson_name">
|
|
<text class="detail-label">销售顾问:</text>
|
|
<text class="detail-value">{{ order.salesperson_name }}</text>
|
|
</view>
|
|
|
|
<view class="detail-row" v-if="order.course_count">
|
|
<text class="detail-label">课时数量:</text>
|
|
<text class="detail-value">{{ order.course_count }}节</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 备注信息 -->
|
|
<view class="order-remark">
|
|
<view class="remark-label">备注:</view>
|
|
<view class="remark-content">{{ order.remark }}</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<!-- 空状态 -->
|
|
<view class="empty-state" v-else>
|
|
<view class="empty-icon">📋</view>
|
|
<view class="empty-text">暂无订单记录</view>
|
|
<view class="empty-tip">客户还未产生任何订单</view>
|
|
<view class="empty-add-btn" @click.stop="handleAddOrder">
|
|
<text>新增订单</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'OrderListCard',
|
|
props: {
|
|
// 订单列表数据
|
|
orderList: {
|
|
type: Array,
|
|
default: () => []
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
// 查看订单详情
|
|
viewOrderDetail(order) {
|
|
this.$emit('view-detail', order)
|
|
},
|
|
|
|
// 新增订单
|
|
handleAddOrder() {
|
|
this.$emit('add-order')
|
|
},
|
|
|
|
// 订单点击处理(根据状态决定是查看详情还是支付)
|
|
handleOrderClick(order) {
|
|
console.log('点击订单:', order)
|
|
// 防护检查:确保order对象存在
|
|
if (!order) {
|
|
console.warn('订单数据不存在,无法处理点击事件')
|
|
return
|
|
}
|
|
|
|
if (order.status === 'pending') {
|
|
// 未支付订单,触发支付
|
|
this.$emit('pay-order', order)
|
|
} else {
|
|
// 已支付订单,查看详情
|
|
this.$emit('view-detail', order)
|
|
}
|
|
},
|
|
|
|
// 获取状态样式类
|
|
getStatusClass(status) {
|
|
if (!status) return 'status-default'
|
|
|
|
const statusMap = {
|
|
'pending': 'status-pending',
|
|
'paid': 'status-paid',
|
|
'partial': 'status-partial',
|
|
'cancelled': 'status-cancelled',
|
|
'completed': 'status-completed',
|
|
'refunded': 'status-refunded'
|
|
}
|
|
return statusMap[status] || 'status-default'
|
|
},
|
|
|
|
// 获取状态文本
|
|
getStatusText(status) {
|
|
if (!status) return '未知状态'
|
|
|
|
const statusMap = {
|
|
'pending': '待支付',
|
|
'paid': '已支付',
|
|
'partial': '部分支付',
|
|
'cancelled': '已取消',
|
|
'completed': '已完成',
|
|
'refunded': '已退款'
|
|
}
|
|
return statusMap[status] || '未知状态'
|
|
},
|
|
|
|
// 格式化时间
|
|
formatTime(timeStr) {
|
|
if (!timeStr) return ''
|
|
try {
|
|
const date = new Date(timeStr)
|
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
|
|
} catch (e) {
|
|
return timeStr
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.order-list-card {
|
|
padding: 0;
|
|
}
|
|
|
|
.order-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24rpx;
|
|
}
|
|
|
|
.order-item {
|
|
background: #3A3A3A;
|
|
border-radius: 16rpx;
|
|
padding: 32rpx;
|
|
border: 1px solid #404040;
|
|
transition: all 0.3s ease;
|
|
position: relative;
|
|
|
|
&:active {
|
|
background: #4A4A4A;
|
|
}
|
|
|
|
&.pending-payment {
|
|
border-color: #FFC107;
|
|
background: rgba(255, 193, 7, 0.05);
|
|
|
|
&::after {
|
|
position: absolute;
|
|
bottom: 16rpx;
|
|
right: 16rpx;
|
|
background: #FFC107;
|
|
color: #000000;
|
|
font-size: 24rpx;
|
|
padding: 8rpx 16rpx;
|
|
border-radius: 20rpx;
|
|
font-weight: 500;
|
|
z-index: 10;
|
|
}
|
|
}
|
|
}
|
|
|
|
.order-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 24rpx;
|
|
padding-bottom: 20rpx;
|
|
border-bottom: 1px solid #404040;
|
|
}
|
|
|
|
.order-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.order-no {
|
|
font-size: 30rpx;
|
|
font-weight: 600;
|
|
color: #ffffff;
|
|
margin-bottom: 8rpx;
|
|
}
|
|
|
|
.order-time {
|
|
font-size: 24rpx;
|
|
color: #999999;
|
|
}
|
|
|
|
.order-status {
|
|
padding: 8rpx 16rpx;
|
|
border-radius: 20rpx;
|
|
font-size: 24rpx;
|
|
font-weight: 500;
|
|
|
|
&.status-pending {
|
|
background: rgba(255, 193, 7, 0.2);
|
|
color: #FFC107;
|
|
}
|
|
|
|
&.status-paid {
|
|
background: rgba(76, 175, 80, 0.2);
|
|
color: #4CAF50;
|
|
}
|
|
|
|
&.status-partial {
|
|
background: rgba(255, 152, 0, 0.2);
|
|
color: #FF9800;
|
|
}
|
|
|
|
&.status-cancelled {
|
|
background: rgba(158, 158, 158, 0.2);
|
|
color: #9E9E9E;
|
|
}
|
|
|
|
&.status-completed {
|
|
background: rgba(41, 211, 180, 0.2);
|
|
color: #29D3B4;
|
|
}
|
|
|
|
&.status-refunded {
|
|
background: rgba(244, 67, 54, 0.2);
|
|
color: #F44336;
|
|
}
|
|
|
|
&.status-default {
|
|
background: rgba(158, 158, 158, 0.2);
|
|
color: #9E9E9E;
|
|
}
|
|
}
|
|
|
|
.order-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.product-info {
|
|
padding: 20rpx;
|
|
background: rgba(41, 211, 180, 0.05);
|
|
border-radius: 12rpx;
|
|
border-left: 4rpx solid #29D3B4;
|
|
}
|
|
|
|
.product-name {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
color: #ffffff;
|
|
margin-bottom: 8rpx;
|
|
}
|
|
|
|
.product-specs {
|
|
font-size: 24rpx;
|
|
color: #cccccc;
|
|
}
|
|
|
|
.order-details {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16rpx;
|
|
}
|
|
|
|
.detail-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.detail-label {
|
|
font-size: 26rpx;
|
|
color: #999999;
|
|
min-width: 140rpx;
|
|
}
|
|
|
|
.detail-value {
|
|
font-size: 26rpx;
|
|
color: #ffffff;
|
|
flex: 1;
|
|
text-align: right;
|
|
|
|
&.price {
|
|
color: #FFC107;
|
|
font-weight: 600;
|
|
font-size: 28rpx;
|
|
}
|
|
|
|
&.paid {
|
|
color: #4CAF50;
|
|
font-weight: 600;
|
|
}
|
|
|
|
&.unpaid {
|
|
color: #F44336;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
|
|
.order-remark {
|
|
padding: 20rpx;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-radius: 12rpx;
|
|
}
|
|
|
|
.remark-label {
|
|
font-size: 24rpx;
|
|
color: #999999;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
|
|
.remark-content {
|
|
font-size: 26rpx;
|
|
color: #cccccc;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.empty-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 80rpx 40rpx;
|
|
text-align: center;
|
|
}
|
|
|
|
.empty-icon {
|
|
font-size: 120rpx;
|
|
margin-bottom: 32rpx;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.empty-text {
|
|
font-size: 32rpx;
|
|
color: #ffffff;
|
|
margin-bottom: 16rpx;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.empty-tip {
|
|
font-size: 26rpx;
|
|
color: #999999;
|
|
line-height: 1.4;
|
|
margin-bottom: 32rpx;
|
|
}
|
|
|
|
.empty-add-btn {
|
|
background: #29D3B4;
|
|
border-radius: 30rpx;
|
|
padding: 16rpx 40rpx;
|
|
color: #ffffff;
|
|
font-size: 28rpx;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
|
|
&:active {
|
|
background: #1fb396;
|
|
}
|
|
}
|
|
|
|
.action-header {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
margin-bottom: 24rpx;
|
|
}
|
|
|
|
.add-order-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
background: #29D3B4;
|
|
border-radius: 30rpx;
|
|
padding: 12rpx 24rpx;
|
|
color: #ffffff;
|
|
font-size: 26rpx;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
|
|
&:active {
|
|
background: #1fb396;
|
|
}
|
|
}
|
|
|
|
.add-icon {
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
margin-right: 8rpx;
|
|
line-height: 1;
|
|
}
|
|
|
|
.add-text {
|
|
font-size: 26rpx;
|
|
}
|
|
</style>
|