|
|
@@ -0,0 +1,406 @@
|
|
|
+<template>
|
|
|
+ <view class="container">
|
|
|
+ <view class="status-tabs">
|
|
|
+ <view
|
|
|
+ v-for="item in statusList"
|
|
|
+ :key="item.value"
|
|
|
+ :class="['tab', currentStatus === item.value ? 'active' : '']"
|
|
|
+ @click="onStatusChange(item.value)"
|
|
|
+ >
|
|
|
+ {{ item.label }}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <scroll-view
|
|
|
+ scroll-y
|
|
|
+ class="order-list"
|
|
|
+ @scrolltolower="onLoadMore"
|
|
|
+ refresher-enabled
|
|
|
+ :refresher-triggered="refreshing"
|
|
|
+ @refresherrefresh="onRefresh"
|
|
|
+ >
|
|
|
+ <view v-if="loading && orders.length === 0" class="loading-wrap">
|
|
|
+ <text class="loading-text">加载中...</text>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="orders.length === 0" class="empty-wrap">
|
|
|
+ <text class="empty-icon">📋</text>
|
|
|
+ <text class="empty-text">暂无订单</text>
|
|
|
+ </view>
|
|
|
+ <view v-else>
|
|
|
+ <view
|
|
|
+ v-for="item in orders"
|
|
|
+ :key="item.id"
|
|
|
+ class="order-card"
|
|
|
+ @click="goDetail(item.orderNo)"
|
|
|
+ >
|
|
|
+ <view class="order-header">
|
|
|
+ <text class="order-no">{{ item.orderNo }}</text>
|
|
|
+ <text :class="['status-badge', 'status-' + item.status]">{{ statusLabel(item.status) }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="order-body">
|
|
|
+ <view class="product-info">
|
|
|
+ <text class="product-name">{{ item.productName }}</text>
|
|
|
+ <text class="product-type">{{ typeLabel(item.productType) }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="order-meta">
|
|
|
+ <text class="order-amount">¥{{ item.totalAmount }}</text>
|
|
|
+ <text class="order-qty">x{{ item.quantity }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="order-footer">
|
|
|
+ <text class="order-time">{{ formatTime(item.createdAt) }}</text>
|
|
|
+ <view class="order-actions">
|
|
|
+ <button
|
|
|
+ v-if="item.status === 'pending'"
|
|
|
+ class="btn-small btn-cancel"
|
|
|
+ @click.stop="onCancel(item.orderNo)"
|
|
|
+ >取消</button>
|
|
|
+ <button
|
|
|
+ v-if="item.status === 'pending'"
|
|
|
+ class="btn-small btn-pay"
|
|
|
+ @click.stop="onPay(item.orderNo)"
|
|
|
+ >支付</button>
|
|
|
+ <button
|
|
|
+ v-if="item.status === 'paid'"
|
|
|
+ class="btn-small btn-confirm"
|
|
|
+ @click.stop="onConfirm(item.orderNo)"
|
|
|
+ >确认完成</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-if="loadingMore" class="loading-more">
|
|
|
+ <text class="loading-text">加载中...</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="noMore && orders.length > 0" class="no-more">
|
|
|
+ <text class="no-more-text">— 没有更多了 —</text>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { productOrderMy, productOrderPay, productOrderCancel, productOrderConfirm } from '@/utils/api.js'
|
|
|
+
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ statusList: [
|
|
|
+ { label: '全部', value: '' },
|
|
|
+ { label: '待支付', value: 'pending' },
|
|
|
+ { label: '已支付', value: 'paid' },
|
|
|
+ { label: '已完成', value: 'completed' },
|
|
|
+ { label: '已取消', value: 'cancelled' }
|
|
|
+ ],
|
|
|
+ currentStatus: '',
|
|
|
+ orders: [],
|
|
|
+ page: 1,
|
|
|
+ size: 20,
|
|
|
+ loading: false,
|
|
|
+ loadingMore: false,
|
|
|
+ refreshing: false,
|
|
|
+ noMore: false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad() {
|
|
|
+ this.loadOrders()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ onStatusChange(value) {
|
|
|
+ this.currentStatus = value
|
|
|
+ this.page = 1
|
|
|
+ this.orders = []
|
|
|
+ this.noMore = false
|
|
|
+ this.loadOrders()
|
|
|
+ },
|
|
|
+ loadOrders() {
|
|
|
+ if (this.loading) return
|
|
|
+ this.loading = true
|
|
|
+ const params = { page: this.page, size: this.size }
|
|
|
+ if (this.currentStatus) {
|
|
|
+ params.status = this.currentStatus
|
|
|
+ }
|
|
|
+ productOrderMy(params).then(res => {
|
|
|
+ this.loading = false
|
|
|
+ this.refreshing = false
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ const list = res.data || []
|
|
|
+ if (this.page === 1) {
|
|
|
+ this.orders = list
|
|
|
+ } else {
|
|
|
+ this.orders = this.orders.concat(list)
|
|
|
+ }
|
|
|
+ this.noMore = list.length < this.size
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ this.loading = false
|
|
|
+ this.refreshing = false
|
|
|
+ })
|
|
|
+ },
|
|
|
+ onRefresh() {
|
|
|
+ this.refreshing = true
|
|
|
+ this.page = 1
|
|
|
+ this.noMore = false
|
|
|
+ this.loadOrders()
|
|
|
+ },
|
|
|
+ onLoadMore() {
|
|
|
+ if (this.noMore || this.loadingMore) return
|
|
|
+ this.page = this.page + 1
|
|
|
+ this.loadingMore = true
|
|
|
+ const params = { page: this.page, size: this.size }
|
|
|
+ if (this.currentStatus) {
|
|
|
+ params.status = this.currentStatus
|
|
|
+ }
|
|
|
+ productOrderMy(params).then(res => {
|
|
|
+ this.loadingMore = false
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ const list = res.data || []
|
|
|
+ this.orders = this.orders.concat(list)
|
|
|
+ this.noMore = list.length < this.size
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ this.loadingMore = false
|
|
|
+ })
|
|
|
+ },
|
|
|
+ goDetail(orderNo) {
|
|
|
+ uni.navigateTo({ url: '/pages/shop/order-detail/order-detail?orderNo=' + orderNo })
|
|
|
+ },
|
|
|
+ onPay(orderNo) {
|
|
|
+ uni.showLoading({ title: '支付中...' })
|
|
|
+ productOrderPay({ orderNo }).then(res => {
|
|
|
+ uni.hideLoading()
|
|
|
+ if (res.code === 200) {
|
|
|
+ uni.showToast({ title: '支付成功', icon: 'success' })
|
|
|
+ this.onRefresh()
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: res.message || '支付失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ uni.hideLoading()
|
|
|
+ })
|
|
|
+ },
|
|
|
+ onCancel(orderNo) {
|
|
|
+ uni.showModal({
|
|
|
+ title: '确认取消',
|
|
|
+ content: '确定取消该订单吗?',
|
|
|
+ success: (confirm) => {
|
|
|
+ if (confirm.confirm) {
|
|
|
+ productOrderCancel({ orderNo }).then(res => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ uni.showToast({ title: '订单已取消', icon: 'success' })
|
|
|
+ this.onRefresh()
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: res.message || '取消失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ onConfirm(orderNo) {
|
|
|
+ productOrderConfirm({ orderNo }).then(res => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ uni.showToast({ title: '订单已完成', icon: 'success' })
|
|
|
+ this.onRefresh()
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: res.message || '操作失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ statusLabel(status) {
|
|
|
+ const map = {
|
|
|
+ pending: '待支付',
|
|
|
+ paid: '已支付',
|
|
|
+ completed: '已完成',
|
|
|
+ cancelled: '已取消',
|
|
|
+ refunded: '已退款'
|
|
|
+ }
|
|
|
+ return map[status] || status
|
|
|
+ },
|
|
|
+ typeLabel(type) {
|
|
|
+ const map = {
|
|
|
+ activity: '活动',
|
|
|
+ course: '课程',
|
|
|
+ physical: '实物',
|
|
|
+ digital: '数字',
|
|
|
+ service: '服务'
|
|
|
+ }
|
|
|
+ return map[type] || type
|
|
|
+ },
|
|
|
+ formatTime(time) {
|
|
|
+ if (!time) return ''
|
|
|
+ const d = new Date(time)
|
|
|
+ return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0')
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100vh;
|
|
|
+ background: #f5f5f5;
|
|
|
+}
|
|
|
+.status-tabs {
|
|
|
+ display: flex;
|
|
|
+ background: #fff;
|
|
|
+ padding: 20rpx 0;
|
|
|
+ border-bottom: 1rpx solid #eee;
|
|
|
+ position: sticky;
|
|
|
+ top: 0;
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+.tab {
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #666;
|
|
|
+ padding: 10rpx 0;
|
|
|
+}
|
|
|
+.tab.active {
|
|
|
+ color: #ff6b6b;
|
|
|
+ font-weight: bold;
|
|
|
+ border-bottom: 4rpx solid #ff6b6b;
|
|
|
+}
|
|
|
+.order-list {
|
|
|
+ flex: 1;
|
|
|
+ height: calc(100vh - 100rpx);
|
|
|
+}
|
|
|
+.loading-wrap,
|
|
|
+.empty-wrap {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding-top: 200rpx;
|
|
|
+}
|
|
|
+.loading-text {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.empty-icon {
|
|
|
+ font-size: 100rpx;
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+}
|
|
|
+.empty-text {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.order-card {
|
|
|
+ background: #fff;
|
|
|
+ margin: 20rpx;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 24rpx;
|
|
|
+ box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
|
|
|
+}
|
|
|
+.order-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+.order-no {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.status-badge {
|
|
|
+ font-size: 22rpx;
|
|
|
+ padding: 4rpx 16rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
+}
|
|
|
+.status-pending {
|
|
|
+ background: #fff7e6;
|
|
|
+ color: #fa8c16;
|
|
|
+}
|
|
|
+.status-paid {
|
|
|
+ background: #e6f7ff;
|
|
|
+ color: #1890ff;
|
|
|
+}
|
|
|
+.status-completed {
|
|
|
+ background: #f6ffed;
|
|
|
+ color: #52c41a;
|
|
|
+}
|
|
|
+.status-cancelled,
|
|
|
+.status-refunded {
|
|
|
+ background: #f5f5f5;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.order-body {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+.product-info {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+.product-name {
|
|
|
+ display: block;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 6rpx;
|
|
|
+}
|
|
|
+.product-type {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.order-meta {
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+.order-amount {
|
|
|
+ display: block;
|
|
|
+ font-size: 30rpx;
|
|
|
+ color: #ff6b6b;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.order-qty {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.order-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ border-top: 1rpx solid #f0f0f0;
|
|
|
+ padding-top: 16rpx;
|
|
|
+}
|
|
|
+.order-time {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.order-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 12rpx;
|
|
|
+}
|
|
|
+.btn-small {
|
|
|
+ font-size: 22rpx;
|
|
|
+ padding: 8rpx 24rpx;
|
|
|
+ border-radius: 30rpx;
|
|
|
+ border: none;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+.btn-small::after {
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+.btn-cancel {
|
|
|
+ background: #f5f5f5;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+.btn-pay {
|
|
|
+ background: linear-gradient(135deg, #ff6b6b, #ff8e53);
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+.btn-confirm {
|
|
|
+ background: #e6f7ff;
|
|
|
+ color: #1890ff;
|
|
|
+}
|
|
|
+.loading-more,
|
|
|
+.no-more {
|
|
|
+ text-align: center;
|
|
|
+ padding: 30rpx;
|
|
|
+}
|
|
|
+.no-more-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #ccc;
|
|
|
+}
|
|
|
+</style>
|