Files
integral-shop/single_uniapp22miao/pages/integral/orders.vue
panchengyong 786bf78282 更新项目配置和添加小程序模块
- 修改 ArticleController.java
- 更新 application.yml 配置
- 更新 frontend/.env.production 环境配置
- 添加 single_uniapp22miao 小程序模块
- 添加 logs 目录
2026-03-13 13:27:13 +08:00

1014 lines
25 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="orders-page">
<!-- 橙色头部 -->
<view class="header-section">
<view class="header-content">
<view class="header-icon" @click="goBack">
<text class="iconfont icon-fanhui"></text>
</view>
<text class="header-title">我的订单</text>
<text class="header-menu"></text>
</view>
</view>
<!-- 状态标签栏 -->
<view class="stats-bar">
<view
v-for="(tab, index) in tabs"
:key="index"
class="stat-item"
:class="{ 'active': currentTab === index }"
@click="switchTab(index)"
>
<view class="stat-number-wrapper">
<text class="stat-number" :class="{ 'active': currentTab === index }">{{ tab.count || 0 }}</text>
<view v-if="currentTab === index" class="stat-underline"></view>
</view>
<text class="stat-text" :class="{ 'active': currentTab === index }">{{ tab.name }}</text>
</view>
</view>
<!-- 订单列表 -->
<scroll-view
class="orders-list"
scroll-y
@scrolltolower="loadMore"
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
>
<view v-if="orderList.length > 0" class="list-container">
<view
v-for="(order, index) in orderList"
:key="order.id"
class="order-item"
@click="goToDetail(order.orderId)"
>
<!-- 订单头部日期和状态 -->
<view class="order-header">
<text class="order-time">{{ order.createTime }}</text>
<text class="order-status" :style="{ color: getStatusColor(order.status, order.paid) }">
{{ order.status_text }}
</text>
</view>
<!-- 商品信息列表 -->
<view class="order-content">
<view
v-for="(goods, gIndex) in order.orderInfoList"
:key="gIndex"
class="goods-row"
>
<image v-if="goods.image" class="goods-image" :src="goods.image" mode="aspectFill"></image>
<view class="goods-info">
<text class="goods-name">{{ goods.storeName || goods.productName }}</text>
<view class="goods-price-row">
<text class="goods-points">{{ formatPoints(goods.integral || goods.vipPrice || goods.price) }}积分</text>
<text class="goods-quantity">x{{ goods.cartNum }}</text>
</view>
</view>
</view>
</view>
<!-- 订单汇总 -->
<view class="order-summary">
<text class="summary-text">{{ order.quantity }}件商品总金额</text>
<text class="summary-points">{{ formatPoints(order.points) }}积分</text>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-else-if="!loading && isShow" class="empty-state">
<image class="empty-image" src="/static/images/empty.png" mode="aspectFit"></image>
<text class="empty-text">暂无订单</text>
<button class="empty-btn" @click="goToShop">去逛逛</button>
</view>
<!-- 加载更多 -->
<view v-if="orderList.length > 0" class="load-more">
<text v-if="loading" class="load-text">加载中...</text>
<text v-else-if="noMore" class="load-text">我也是有底线的</text>
</view>
</scroll-view>
<!-- 底部导航栏 -->
<view class="bottom-tabbar safe-area-inset-bottom">
<view class="tab-item" @click="goToShop">
<image class="tab-icon" src="/static/tabBar/home.png" mode="aspectFit"></image>
<text class="tab-text">积分兑换</text>
</view>
<view class="tab-item" @click="goToCart">
<view class="tab-icon-wrap">
<image class="tab-icon" src="/static/tabBar/cart.png" mode="aspectFit"></image>
<view class="cart-badge" v-if="cartCount > 0">
<text>{{ cartCount > 99 ? '99+' : cartCount }}</text>
</view>
</view>
<text class="tab-text">购物车</text>
</view>
<view class="tab-item active">
<image class="tab-icon" src="/static/tabBar/order-active.png" mode="aspectFit"></image>
<text class="tab-text active">订单</text>
</view>
</view>
</view>
</template>
<script>
import { getOrderList, orderData, orderCancel, orderTake, orderDel, getCartCounts } from '@/api/order.js';
import { mapGetters } from 'vuex';
import { toLogin } from '@/libs/login.js';
export default {
data() {
return {
// 状态标签: type对应后端参数
// -99-全部 0-待兑换(待付款) 1-待发货 2-待收货 3-已完成
tabs: [
{ name: '全部', type: -99, count: 0 },
{ name: '待兑换', type: 0, count: 0 },
{ name: '待发货', type: 1, count: 0 },
{ name: '待收货', type: 2, count: 0 },
{ name: '已完成', type: 4, count: 0 }
],
currentTab: 0, // 默认显示"全部"
orderList: [],
orderData: {}, // 订单统计数据
cartCount: 0, // 购物车数量
page: 1,
limit: 10,
loading: false,
refreshing: false,
noMore: false,
isShow: false
}
},
computed: {
...mapGetters(['isLogin'])
},
onLoad(options) {
if (options.status !== undefined) {
const status = parseInt(options.status);
// 根据状态找到对应的标签索引
const tabIndex = this.tabs.findIndex(tab => tab.type === status);
if (tabIndex >= 0) {
this.currentTab = tabIndex;
}
} else {
// 默认显示"全部"标签索引0
this.currentTab = 0;
}
},
onShow() {
// 页面显示时刷新订单统计和列表
if (this.isLogin) {
this.noMore = false;
this.page = 1;
this.orderList = [];
this.getOrderData();
this.loadOrderList();
this.getCartNum();
} else {
toLogin();
}
},
onReachBottom() {
this.loadMore();
},
methods: {
// 初始化页面
async initPage() {
// 检查登录状态
if (!this.isLogin) {
toLogin();
return;
}
// 加载订单统计
await this.getOrderData();
// 加载订单列表
this.loadOrderList();
},
// 获取订单统计数据
async getOrderData() {
try {
const res = await orderData();
if (res.data) {
this.orderData = res.data;
// 更新各个状态的订单数量
// 当前标签: 全部(-99), 待兑换(0), 待发货(1), 待收货(2), 已完成(4)
const unPaidCount = res.data.unPaidCount || 0; // 待兑换(待付款)
const unShippedCount = res.data.unShippedCount || 0;
const receivedCount = res.data.receivedCount || 0;
const evaluateCount = res.data.evaluateCount || 0; // 待评价
const completeCount = res.data.completeCount || 0; // 已完成
// "已完成"标签:包含待评价(type:3) + 已完成(type:4)
const finishedCount = evaluateCount + completeCount;
// 计算全部订单数量
const totalCount = res.data.orderCount || (unPaidCount + unShippedCount + receivedCount + finishedCount);
this.tabs[0].count = totalCount; // 全部
this.tabs[1].count = unPaidCount; // 待兑换
this.tabs[2].count = unShippedCount; // 待发货
this.tabs[3].count = receivedCount; // 待收货
this.tabs[4].count = finishedCount; // 已完成(包含待评价)
}
} catch (error) {
console.error('加载订单统计失败:', error);
}
},
// 获取购物车数量
async getCartNum() {
try {
const res = await getCartCounts(true, 'total');
if (res.data) {
this.cartCount = res.data.count || 0;
}
} catch (error) {
console.error('获取购物车数量失败:', error);
}
},
// 返回上一页
goBack() {
uni.navigateBack()
},
// 切换标签
switchTab(index) {
if (this.currentTab === index) return
this.currentTab = index
this.resetList()
this.loadOrderList()
},
// 重置列表
resetList() {
this.orderList = []
this.page = 1
this.noMore = false
},
// 加载订单列表
async loadOrderList() {
if (this.noMore) return
if (this.loading) return
// 检查登录状态
if (!this.isLogin) {
toLogin();
return;
}
this.loading = true
try {
const tab = this.tabs[this.currentTab];
const params = {
page: this.page,
limit: this.limit
};
let res;
// "全部"时传空字符串其他传具体type值
if (tab.type === -99) {
params.type = '-99'; // 空字符串表示查询所有状态
res = await getOrderList(params);
} else if (tab.type === 4) {
// "已完成"标签:同时获取 type=3(待评价) 和 type=4(已完成) 的订单
const [res3, res4] = await Promise.all([
getOrderList({ ...params, type: 3 }),
getOrderList({ ...params, type: 4 })
]);
// 合并两个结果
const list3 = (res3.data && res3.data.list) || [];
const list4 = (res4.data && res4.data.list) || [];
const mergedList = [...list3, ...list4];
// 按创建时间倒序排序
mergedList.sort((a, b) => {
const timeA = new Date(a.createTime || 0).getTime();
const timeB = new Date(b.createTime || 0).getTime();
return timeB - timeA;
});
// 构造合并后的响应
res = {
data: {
list: mergedList
}
};
} else {
params.type = tab.type;
res = await getOrderList(params);
}
if (res.data) {
const list = res.data.list || [];
// 格式化订单数据
const formattedList = list.map(order => this.formatOrderData(order));
if (this.page === 1) {
this.orderList = formattedList;
} else {
this.orderList = [...this.orderList, ...formattedList];
}
if (list.length < this.limit) {
this.noMore = true;
}
this.page++;
this.isShow = true;
} else {
uni.showToast({
title: res.msg || '加载失败',
icon: 'none'
});
}
} catch (error) {
console.error('加载订单失败:', error)
uni.showToast({
title: error.msg || '加载失败,请重试',
icon: 'none'
})
} finally {
this.loading = false
this.refreshing = false
}
},
// 格式化订单数据
formatOrderData(order) {
// 获取商品列表
const orderInfoList = order.orderInfoList || [];
// 计算总商品数量
let totalNum = order.totalNum || 1;
// 计算总积分优先使用payIntegral/useIntegral否则从商品列表计算
let totalPoints = 0;
// 优先使用订单级别的积分字段
if (order.payIntegral !== undefined && order.payIntegral !== null) {
totalPoints = parseFloat(order.payIntegral) || 0;
} else if (order.useIntegral !== undefined && order.useIntegral !== null) {
totalPoints = parseFloat(order.useIntegral) || 0;
} else if (order.integral !== undefined && order.integral !== null) {
totalPoints = parseFloat(order.integral) || 0;
} else {
// 从商品列表计算总积分
totalPoints = orderInfoList.reduce((sum, item) => {
// 优先使用integral字段否则使用vipPrice或price
const itemPoints = parseFloat(item.integral) || parseFloat(item.vipPrice) || parseFloat(item.price) || 0;
const quantity = parseInt(item.cartNum) || 1;
return sum + (itemPoints * quantity);
}, 0);
// 如果计算结果为0尝试使用payPrice或totalPrice
if (totalPoints === 0) {
totalPoints = parseFloat(order.payPrice) || parseFloat(order.totalPrice) || 0;
}
}
// 获取第一个商品的信息用于列表显示
let goodsName = '商品名称';
let goodsImage = '/static/images/default-goods.png';
let goodsPrice = 0;
let goodsNum = 1;
if (orderInfoList.length > 0) {
const firstGoods = orderInfoList[0];
goodsName = firstGoods.storeName || firstGoods.productName || goodsName;
goodsImage = firstGoods.image || goodsImage;
// 商品积分优先使用integral否则使用vipPrice或price
goodsPrice = parseFloat(firstGoods.integral) || parseFloat(firstGoods.vipPrice) || parseFloat(firstGoods.price) || 0;
goodsNum = firstGoods.cartNum || 1;
}
console.log('订单积分计算:', {
orderId: order.orderId,
payIntegral: order.payIntegral,
useIntegral: order.useIntegral,
integral: order.integral,
payPrice: order.payPrice,
totalPrice: order.totalPrice,
calculatedPoints: totalPoints,
orderInfoList: orderInfoList.map(item => ({
name: item.storeName,
integral: item.integral,
vipPrice: item.vipPrice,
price: item.price,
cartNum: item.cartNum
}))
});
return {
id: order.id,
orderId: order.orderId || order.orderNo || '',
order_no: order.orderId || order.orderNo || '',
goods_name: goodsName,
goods_image: goodsImage,
quantity: totalNum,
points: totalPoints,
goods_points: goodsPrice,
goods_quantity: goodsNum,
status: order.status,
paid: order.paid,
status_text: order.orderStatus || this.getStatusText(order.status, order.paid),
orderInfoList: orderInfoList,
createTime: order.createTime || '',
created_at: order.createTime || '',
activityType: order.activityType || '普通'
};
},
// 获取状态文本
getStatusText(status, paid) {
if (!paid) return '待兑换';
// status: 0-待发货 1-待收货 2-待评价 3-已完成
const statusMap = {
0: '待发货',
1: '待收货',
2: '待评价',
3: '已完成'
};
return statusMap[status] || '未知状态';
},
// 下拉刷新
onRefresh() {
this.refreshing = true
this.resetList()
this.getOrderData()
this.loadOrderList()
},
// 加载更多
loadMore() {
if (!this.noMore && !this.loading) {
this.loadOrderList()
}
},
// 取消订单
cancelOrder(orderId, index) {
uni.showModal({
title: '提示',
content: '确定要取消这个订单吗?',
confirmColor: '#f55850',
success: async (res) => {
if (res.confirm) {
if (!this.isLogin) {
toLogin();
return;
}
uni.showLoading({ title: '取消中...' })
try {
const result = await orderCancel(orderId);
uni.hideLoading()
uni.showToast({
title: '取消成功',
icon: 'success'
})
// 从列表中移除该订单
if (index !== undefined) {
this.orderList.splice(index, 1);
}
// 刷新统计
this.getOrderData();
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.msg || error.message || '取消失败,请重试',
icon: 'none'
})
}
}
}
})
},
// 确认收货
confirmReceipt(orderId, index) {
uni.showModal({
title: '确认收货',
content: '确认已收到商品?',
confirmColor: '#f55850',
success: async (res) => {
if (res.confirm) {
if (!this.isLogin) {
toLogin();
return;
}
uni.showLoading({ title: '确认中...' })
try {
const result = await orderTake(orderId);
uni.hideLoading()
uni.showToast({
title: '确认收货成功',
icon: 'success'
})
// 刷新统计和列表
this.getOrderData();
this.resetList();
this.loadOrderList();
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.msg || error.message || '操作失败,请重试',
icon: 'none'
})
}
}
}
})
},
// 删除订单
delOrder(orderId, index) {
uni.showModal({
title: '提示',
content: '确定要删除这个订单吗?',
confirmColor: '#f55850',
success: async (res) => {
if (res.confirm) {
try {
await orderDel(orderId);
uni.showToast({
title: '删除成功',
icon: 'success'
})
// 从列表中移除该订单
if (index !== undefined) {
this.orderList.splice(index, 1);
}
// 刷新统计
this.getOrderData();
} catch (error) {
uni.showToast({
title: error.msg || '删除失败',
icon: 'none'
})
}
}
}
})
},
// 查看物流
viewLogistics(order) {
if (!order.express_no) {
uni.showToast({
title: '暂无物流信息',
icon: 'none'
})
return
}
// 跳转到积分订单详情页面查看物流
uni.navigateTo({
url: `/pages/integral/order-detail?id=${order.orderId}`
})
},
// 跳转订单详情
goToDetail(orderId) {
if (!orderId) return;
uni.navigateTo({
url: `/pages/integral/order-detail?id=${orderId}`
})
},
// 去评价
goComment(order) {
if (!order.orderInfoList || order.orderInfoList.length === 0) {
uni.showToast({
title: '商品信息不存在',
icon: 'none'
})
return
}
const firstGoods = order.orderInfoList[0]
uni.navigateTo({
url: `/pages/goods/goods_comment_con/index?unique=${firstGoods.attrId}&orderId=${order.orderId}&id=${order.id}`
})
},
// 去商城
goToShop() {
uni.navigateTo({
url: '/pages/integral/index'
})
},
// 去购物车
goToCart() {
uni.navigateTo({
url: '/pages/integral/cart'
})
},
// 去支付
goPay(order) {
uni.navigateTo({
url: `/pages/order/order_payment/index?orderNo=${order.orderId}&payPrice=${order.points}`
})
},
// 格式化积分
formatPoints(price) {
if (!price && price !== 0) return '0'
// 积分显示为整数,添加千分位
return Number(price).toFixed(0).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
},
// 获取状态颜色
getStatusColor(status, paid) {
if (!paid) return '#F54900'; // 待付款 - 橙色
// status: 0-待发货 1-待收货 2-待评价 3-已完成
const colors = {
0: '#F54900', // 待发货 - 橙色
1: '#F54900', // 待收货 - 橙色
2: '#F54900', // 待评价 - 橙色
3: '#52c41a', // 已完成 - 绿色
}
return colors[status] || '#F54900'
},
}
}
</script>
<style lang="scss" scoped>
.orders-page {
min-height: 100vh;
background-color: #F9FAFB;
display: flex;
flex-direction: column;
padding-bottom: 120rpx;
}
// 橙色头部
.header-section {
background-color: #F54900;
padding: 0;
}
.header-content {
height: 112rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
}
.header-icon {
width: 72rpx;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 36rpx;
color: #FFFFFF;
}
}
.header-title {
flex: 1;
font-size: 36rpx;
color: #FFFFFF;
font-weight: normal;
text-align: center;
line-height: 56rpx;
}
.header-menu {
font-size: 32rpx;
color: #FFFFFF;
font-weight: bold;
letter-spacing: 2rpx;
width: 72rpx;
text-align: right;
}
// 状态统计栏
.stats-bar {
background-color: #FFFFFF;
display: flex;
padding: 32rpx 0;
align-items: center;
justify-content: space-around;
}
.stat-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
}
.stat-number-wrapper {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.stat-number {
font-size: 40rpx;
color: #333333;
font-weight: 500;
line-height: 1.2;
&.active {
color: #F54900;
}
}
.stat-underline {
position: absolute;
bottom: -4rpx;
left: 50%;
transform: translateX(-50%);
width: 24rpx;
height: 4rpx;
background-color: #F54900;
}
.stat-text {
font-size: 26rpx;
color: #666666;
font-weight: normal;
line-height: 1.2;
&.active {
color: #F54900;
}
}
.orders-list {
flex: 1;
padding: 24rpx 32rpx;
background-color: #F9FAFB;
}
.list-container {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.order-item {
background-color: #FFFFFF;
border-radius: 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 32rpx;
border-bottom: 1rpx solid #F5F5F5;
}
.order-time {
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
}
.order-status {
font-size: 28rpx;
font-weight: normal;
color: #F54900;
line-height: 40rpx;
}
.order-content {
padding: 0 32rpx;
}
.goods-row {
display: flex;
padding: 24rpx 0;
gap: 20rpx;
border-bottom: 1rpx solid #F5F5F5;
&:last-child {
border-bottom: none;
}
}
.goods-image {
width: 100rpx;
height: 100rpx;
border-radius: 12rpx;
background-color: #F5F5F5;
flex-shrink: 0;
}
.goods-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
gap: 12rpx;
}
.goods-name {
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
font-weight: normal;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
line-clamp: 1;
overflow: hidden;
}
.goods-price-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.goods-points {
font-size: 28rpx;
color: #F54900;
font-weight: normal;
line-height: 40rpx;
}
.goods-quantity {
font-size: 26rpx;
color: #999999;
line-height: 40rpx;
}
.order-summary {
padding: 24rpx 32rpx;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 8rpx;
}
.summary-text {
font-size: 26rpx;
color: #333333;
line-height: 40rpx;
}
.summary-points {
font-size: 28rpx;
color: #F54900;
font-weight: 500;
line-height: 40rpx;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
}
.empty-image {
width: 300rpx;
height: 300rpx;
margin-bottom: 40rpx;
}
.empty-text {
font-size: 28rpx;
color: #999999;
margin-bottom: 40rpx;
}
.empty-btn {
width: 200rpx;
height: 80rpx;
line-height: 80rpx;
background-color: #F54900;
color: #FFFFFF;
border-radius: 40rpx;
font-size: 28rpx;
border: none;
&::after {
border: none;
}
}
.load-more {
padding: 40rpx 0;
text-align: center;
}
.load-text {
font-size: 28rpx;
color: #999999;
line-height: 40rpx;
}
// 底部导航栏
.bottom-tabbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 100rpx;
background-color: #FFFFFF;
display: flex;
border-top: 1rpx solid #EEEEEE;
z-index: 100;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4rpx;
}
.tab-icon-wrap {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.tab-icon {
width: 44rpx;
height: 44rpx;
}
.cart-badge {
position: absolute;
top: -8rpx;
right: -16rpx;
min-width: 32rpx;
height: 32rpx;
background-color: #F54900;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 8rpx;
text {
font-size: 20rpx;
color: #FFFFFF;
font-weight: 500;
}
}
.tab-text {
font-size: 22rpx;
color: #999999;
&.active {
color: #F54900;
}
}
</style>