feat:兑换中心
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import request from '../../../../utils/request';
|
||||
|
||||
const STATUS_MAP = {
|
||||
1: { text: '待处理', theme: 'warning' },
|
||||
2: { text: '处理中', theme: 'primary' },
|
||||
3: { text: '已发货', theme: 'primary' },
|
||||
4: { text: '已完成', theme: 'success' },
|
||||
5: { text: '已取消', theme: 'default' }
|
||||
};
|
||||
|
||||
Page({
|
||||
data: {
|
||||
orders: [],
|
||||
isLoading: true,
|
||||
hasMore: true,
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
activeStatus: 0,
|
||||
statusTabs: [
|
||||
{ key: 0, label: '全部' },
|
||||
{ key: 1, label: '待处理' },
|
||||
{ key: 4, label: '已完成' },
|
||||
{ key: 5, label: '已取消' }
|
||||
]
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.fetchOrders();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.fetchOrders();
|
||||
},
|
||||
|
||||
async fetchOrders(append = false) {
|
||||
if (!append) {
|
||||
this.setData({ isLoading: true, current: 1, orders: [] });
|
||||
}
|
||||
try {
|
||||
const params = {
|
||||
current: this.data.current,
|
||||
pageSize: this.data.pageSize
|
||||
};
|
||||
if (this.data.activeStatus) {
|
||||
params.status = this.data.activeStatus;
|
||||
}
|
||||
const res = await request.get('/exchange/orders', params);
|
||||
let list = (res && res.list) ? res.list : [];
|
||||
const total = (res && res.total) ? res.total : 0;
|
||||
|
||||
// Enrich with status display info
|
||||
list = list.map(order => ({
|
||||
...order,
|
||||
statusInfo: STATUS_MAP[order.status] || { text: '未知', theme: 'default' }
|
||||
}));
|
||||
|
||||
this.setData({
|
||||
orders: append ? [...this.data.orders, ...list] : list,
|
||||
hasMore: this.data.orders.length + list.length < total
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Fetch orders failed', e);
|
||||
} finally {
|
||||
this.setData({ isLoading: false });
|
||||
}
|
||||
},
|
||||
|
||||
switchStatus(e) {
|
||||
const key = e.currentTarget.dataset.key;
|
||||
if (key === this.data.activeStatus) return;
|
||||
this.setData({ activeStatus: key });
|
||||
this.fetchOrders();
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
if (!this.data.hasMore || this.data.isLoading) return;
|
||||
this.setData({ current: this.data.current + 1 });
|
||||
this.fetchOrders(true);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"navigationBarTitleText": "兑换记录",
|
||||
"disableScroll": true,
|
||||
"usingComponents": {
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading",
|
||||
"t-tag": "tdesign-miniprogram/tag/tag"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<view class="orders-page">
|
||||
|
||||
<!-- Status Tabs -->
|
||||
<view class="status-tabs-wrap">
|
||||
<scroll-view class="status-tabs" scroll-x enable-flex show-scrollbar="{{false}}">
|
||||
<view wx:for="{{statusTabs}}" wx:key="key"
|
||||
class="status-tab {{activeStatus === item.key ? 'active' : ''}}"
|
||||
bindtap="switchStatus" data-key="{{item.key}}">
|
||||
<text>{{item.label}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="page-scroll" show-scrollbar="{{false}}" enhanced="{{true}}" bindscrolltolower="onReachBottom">
|
||||
|
||||
<view class="orders-list" wx:if="{{orders.length > 0}}">
|
||||
<view class="order-card" wx:for="{{orders}}" wx:key="id">
|
||||
<view class="order-header">
|
||||
<text class="order-time">{{item.createdAtStr}}</text>
|
||||
<t-tag size="small" variant="light" theme="{{item.statusInfo.theme}}">
|
||||
{{item.statusInfo.text}}
|
||||
</t-tag>
|
||||
</view>
|
||||
|
||||
<view class="order-body">
|
||||
<view class="order-img-wrap">
|
||||
<image wx:if="{{item.item && item.item.image}}" src="{{item.item.image.url}}" mode="aspectFill" class="order-img" />
|
||||
<view wx:else class="order-img-placeholder">
|
||||
<t-icon name="gift" size="48rpx" color="#C5E1A5" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="order-info">
|
||||
<text class="order-item-name">{{item.itemName}}</text>
|
||||
<text class="order-type-tag">{{item.itemType === 'PHYSICAL' ? '实物' : (item.itemType === 'VIRTUAL' ? '虚拟' : '优惠券')}}</text>
|
||||
<view class="order-cost-row">
|
||||
<text class="order-cost-sun">☀️</text>
|
||||
<text class="order-cost-val">-{{item.costSunlight}}</text>
|
||||
<text class="order-qty" wx:if="{{item.quantity > 1}}">x{{item.quantity}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Shipping Info (if physical and shipped) -->
|
||||
<view class="order-shipping" wx:if="{{item.trackingNo}}">
|
||||
<t-icon name="deliver" size="28rpx" color="#558B2F" />
|
||||
<text class="tracking-text">快递单号: {{item.trackingNo}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Loading / Empty -->
|
||||
<view class="state-footer" wx:if="{{isLoading}}">
|
||||
<t-loading theme="circular" size="40rpx" text="加载中..." inherit-color />
|
||||
</view>
|
||||
<view class="empty-state" wx:elif="{{!isLoading && orders.length === 0}}">
|
||||
<text class="empty-icon">📦</text>
|
||||
<text class="empty-title">暂无兑换记录</text>
|
||||
<text class="empty-desc">去兑换中心挑选心仪好礼吧</text>
|
||||
</view>
|
||||
<view class="state-footer" wx:elif="{{!hasMore && orders.length > 0}}">
|
||||
<text class="no-more">— 已经到底啦 —</text>
|
||||
</view>
|
||||
|
||||
<view style="height: 60rpx;"></view>
|
||||
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
page {
|
||||
background: #F4F6F0;
|
||||
}
|
||||
|
||||
.orders-page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* ========== Status Tabs ========== */
|
||||
.status-tabs-wrap {
|
||||
flex-shrink: 0;
|
||||
background: #F4F6F0;
|
||||
padding: 20rpx 0 12rpx;
|
||||
}
|
||||
|
||||
.status-tabs {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
padding-left: 32rpx;
|
||||
height: 88rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.status-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 28rpx;
|
||||
height: 72rpx;
|
||||
background: #fff;
|
||||
border-radius: 36rpx;
|
||||
margin-right: 16rpx;
|
||||
font-size: 26rpx;
|
||||
color: #546E7A;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.04);
|
||||
transition: all 0.2s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
flex-shrink: 0;
|
||||
border: 2rpx solid transparent;
|
||||
}
|
||||
|
||||
.status-tab:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.status-tab.active {
|
||||
background: #558B2F;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 8rpx 20rpx rgba(85, 139, 47, 0.3);
|
||||
border-color: #558B2F;
|
||||
}
|
||||
|
||||
/* ========== Orders List ========== */
|
||||
.orders-list {
|
||||
padding: 12rpx 32rpx;
|
||||
}
|
||||
|
||||
.order-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
.order-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #F3F4F6;
|
||||
}
|
||||
|
||||
.order-time {
|
||||
font-size: 24rpx;
|
||||
color: #9CA3AF;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.order-body {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.order-img-wrap {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
background: #F8FAF5;
|
||||
}
|
||||
|
||||
.order-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.order-img-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #F1F8E9, #E8F5E9);
|
||||
}
|
||||
|
||||
.order-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.order-item-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.order-type-tag {
|
||||
display: inline-block;
|
||||
width: fit-content;
|
||||
font-size: 20rpx;
|
||||
color: #6B7280;
|
||||
background: #F3F4F6;
|
||||
padding: 2rpx 14rpx;
|
||||
border-radius: 10rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.order-cost-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.order-cost-sun {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.order-cost-val {
|
||||
font-size: 28rpx;
|
||||
font-weight: 800;
|
||||
color: #E65100;
|
||||
}
|
||||
|
||||
.order-qty {
|
||||
font-size: 22rpx;
|
||||
color: #9CA3AF;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
/* Shipping */
|
||||
.order-shipping {
|
||||
margin-top: 16rpx;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1rpx solid #F3F4F6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.tracking-text {
|
||||
font-size: 24rpx;
|
||||
color: #558B2F;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ========== Empty & Footer States ========== */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 40rpx 80rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 100rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #374151;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 24rpx;
|
||||
color: #9CA3AF;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.state-footer {
|
||||
padding: 40rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
font-size: 24rpx;
|
||||
color: #D1D5DB;
|
||||
font-weight: 500;
|
||||
}
|
||||
Reference in New Issue
Block a user