uni-app 订单系统
核心知识点
1. 订单系统架构
- 前端组件:订单创建、订单列表、订单详情、订单操作
- 后端服务:订单 API、订单处理、状态管理
- 数据层:订单数据模型、状态流转、历史记录
- 支付集成:支付网关对接、订单支付、退款处理
2. 订单生命周期
- 订单创建:购物车转订单、直接创建订单
- 订单待支付:等待用户支付、支付超时处理
- 订单处理中:商家确认、商品准备、物流安排
- 订单已完成:交易完成、评价、售后
- 订单已取消:用户取消、系统取消、超时取消
3. 订单状态管理
- 状态定义:明确订单状态及流转规则
- 状态更新:前端状态同步、后端状态变更
- 状态监听:实时监听订单状态变化
- 状态回滚:处理异常情况的状态回滚
4. 订单支付流程
- 支付方式集成:微信支付、支付宝支付、银行卡支付
- 支付请求:生成支付参数、调用支付接口
- 支付回调:处理支付结果、更新订单状态
- 支付异常:处理支付失败、重复支付
5. 订单管理功能
- 订单列表:按状态、时间、类型筛选
- 订单详情:商品信息、收货信息、支付信息
- 订单操作:取消订单、确认收货、申请退款
- 订单统计:订单数量、金额、状态分布
实用案例
实现电商订单系统
1. 订单创建组件
<template>
<view class="order-create">
<view class="section">
<text class="section-title">收货信息</text>
<view class="address-info" @click="selectAddress">
<uni-icons type="location" size="24" color="#FF6600" />
<view class="address-content" v-if="selectedAddress">
<text class="consignee">{{ selectedAddress.consignee }} {{ selectedAddress.phone }}</text>
<text class="address-detail">{{ selectedAddress.province }}{{ selectedAddress.city }}{{ selectedAddress.district }}{{ selectedAddress.detail }}</text>
</view>
<view class="address-content" v-else>
<text class="no-address">请选择收货地址</text>
</view>
<uni-icons type="arrowright" size="20" color="#999" />
</view>
</view>
<view class="section">
<text class="section-title">商品信息</text>
<view class="product-list">
<view
v-for="(product, index) in products"
:key="index"
class="product-item"
>
<image :src="product.image" mode="aspectFill" class="product-image" />
<view class="product-info">
<text class="product-title">{{ product.title }}</text>
<text class="product-spec">{{ product.spec }}</text>
<view class="product-price-count">
<text class="product-price">¥{{ product.price.toFixed(2) }}</text>
<text class="product-count">x{{ product.count }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="section">
<text class="section-title">支付方式</text>
<uni-radio-group v-model="selectedPayment">
<label
v-for="(payment, index) in paymentMethods"
:key="index"
class="payment-item"
>
<uni-radio :value="payment.value" />
<text class="payment-label">{{ payment.label }}</text>
</label>
</uni-radio-group>
</view>
<view class="section">
<text class="section-title">订单备注</text>
<textarea
v-model="remark"
placeholder="请输入订单备注"
placeholder-class="placeholder"
class="remark-input"
/>
</view>
<view class="order-summary">
<view class="summary-item">
<text class="label">商品金额</text>
<text class="value">¥{{ subtotal.toFixed(2) }}</text>
</view>
<view class="summary-item">
<text class="label">运费</text>
<text class="value">¥{{ shippingFee.toFixed(2) }}</text>
</view>
<view class="summary-item total">
<text class="label">订单总额</text>
<text class="value">¥{{ total.toFixed(2) }}</text>
</view>
</view>
<view class="bottom-bar">
<view class="total-price">
<text class="label">合计:</text>
<text class="price">¥{{ total.toFixed(2) }}</text>
</view>
<button class="submit-btn" @click="submitOrder">提交订单</button>
</view>
</view>
</template>
<script>
import orderApi from '@/api/order';
export default {
data() {
return {
selectedAddress: null,
products: [],
selectedPayment: 'wechat',
paymentMethods: [
{ label: '微信支付', value: 'wechat' },
{ label: '支付宝', value: 'alipay' },
{ label: '银行卡', value: 'bank' }
],
remark: '',
subtotal: 0,
shippingFee: 0,
total: 0
};
},
onLoad(options) {
// 从购物车或商品详情页获取商品信息
if (options.products) {
this.products = JSON.parse(options.products);
} else {
// 模拟商品数据
this.products = [
{
id: 1,
title: 'uni-app 开发实战',
spec: '纸质书',
price: 59.90,
count: 1,
image: 'https://example.com/book1.jpg'
},
{
id: 2,
title: 'Vue.js 实战',
spec: '纸质书',
price: 49.90,
count: 1,
image: 'https://example.com/book2.jpg'
}
];
}
this.calculatePrice();
this.loadDefaultAddress();
},
methods: {
calculatePrice() {
// 计算商品总价
this.subtotal = this.products.reduce((sum, product) => {
return sum + product.price * product.count;
}, 0);
// 计算运费
this.shippingFee = this.subtotal >= 99 ? 0 : 10;
// 计算订单总额
this.total = this.subtotal + this.shippingFee;
},
loadDefaultAddress() {
// 实际项目中应该从地址管理中获取默认地址
this.selectedAddress = {
id: 1,
consignee: '张三',
phone: '13800138000',
province: '北京市',
city: '北京市',
district: '朝阳区',
detail: '望京SOHO T1 C座 2801'
};
},
selectAddress() {
// 跳转到地址选择页面
uni.navigateTo({
url: '/pages/address/select',
success: (res) => {
res.eventChannel.on('selectAddress', (data) => {
this.selectedAddress = data.address;
});
}
});
},
submitOrder() {
if (!this.selectedAddress) {
uni.showToast({
title: '请选择收货地址',
icon: 'none'
});
return;
}
uni.showLoading({
title: '提交订单中...'
});
// 构建订单数据
const orderData = {
addressId: this.selectedAddress.id,
products: this.products.map(product => ({
productId: product.id,
quantity: product.count,
price: product.price
})),
paymentMethod: this.selectedPayment,
remark: this.remark,
amount: {
subtotal: this.subtotal,
shippingFee: this.shippingFee,
total: this.total
}
};
orderApi.createOrder(orderData).then(res => {
if (res.success) {
const orderId = res.data.orderId;
// 跳转到支付页面
uni.navigateTo({
url: `/pages/payment/index?orderId=${orderId}&amount=${this.total}`
});
} else {
uni.showToast({
title: '订单创建失败',
icon: 'none'
});
}
}).catch(error => {
console.error('提交订单失败:', error);
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'none'
});
}).finally(() => {
uni.hideLoading();
});
}
}
};
</script>
<style scoped>
.order-create {
min-height: 100vh;
background-color: #F5F5F5;
padding-bottom: 120rpx;
}
.section {
background-color: #FFFFFF;
margin-bottom: 10rpx;
padding: 20rpx;
}
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.address-info {
display: flex;
align-items: flex-start;
padding: 15rpx 0;
}
.address-content {
flex: 1;
margin-left: 15rpx;
}
.consignee {
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
display: block;
}
.address-detail {
font-size: 24rpx;
color: #666;
line-height: 1.4;
}
.no-address {
font-size: 28rpx;
color: #999;
}
.product-list {
padding: 10rpx 0;
}
.product-item {
display: flex;
margin-bottom: 20rpx;
}
.product-image {
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
}
.product-info {
flex: 1;
margin-left: 15rpx;
}
.product-title {
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.product-spec {
font-size: 24rpx;
color: #999;
margin-bottom: 15rpx;
}
.product-price-count {
display: flex;
justify-content: space-between;
align-items: center;
}
.product-price {
font-size: 28rpx;
color: #FF6600;
font-weight: bold;
}
.product-count {
font-size: 24rpx;
color: #666;
}
.payment-item {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.payment-label {
margin-left: 15rpx;
font-size: 26rpx;
color: #333;
}
.remark-input {
width: 100%;
height: 150rpx;
padding: 15rpx;
border: 1rpx solid #E0E0E0;
border-radius: 8rpx;
font-size: 26rpx;
color: #333;
background-color: #F9F9F9;
}
.placeholder {
color: #999;
}
.order-summary {
background-color: #FFFFFF;
margin-bottom: 10rpx;
padding: 20rpx;
}
.summary-item {
display: flex;
justify-content: space-between;
margin-bottom: 15rpx;
font-size: 26rpx;
}
.summary-item.total {
font-weight: bold;
margin-top: 10rpx;
padding-top: 15rpx;
border-top: 1rpx solid #E0E0E0;
}
.label {
color: #666;
}
.value {
color: #333;
}
.summary-item.total .value {
color: #FF6600;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
background-color: #FFFFFF;
border-top: 1rpx solid #E0E0E0;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.total-price {
display: flex;
align-items: center;
}
.total-price .label {
font-size: 26rpx;
color: #333;
}
.total-price .price {
font-size: 32rpx;
color: #FF6600;
font-weight: bold;
margin-left: 10rpx;
}
.submit-btn {
width: 200rpx;
height: 80rpx;
background-color: #FF6600;
color: #FFFFFF;
border-radius: 40rpx;
font-size: 28rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>2. 订单 API 服务
// api/order.js
import request from './request';
export default {
// 创建订单
async createOrder(orderData) {
return request({
url: '/api/order/create',
method: 'POST',
data: orderData
});
},
// 获取订单列表
async getOrderList(params = {}) {
return request({
url: '/api/order/list',
method: 'GET',
params: {
status: '',
page: 1,
pageSize: 10,
...params
}
});
},
// 获取订单详情
async getOrderDetail(orderId) {
return request({
url: `/api/order/detail/${orderId}`,
method: 'GET'
});
},
// 取消订单
async cancelOrder(orderId, reason = '') {
return request({
url: `/api/order/cancel/${orderId}`,
method: 'POST',
data: {
reason
}
});
},
// 确认收货
async confirmReceipt(orderId) {
return request({
url: `/api/order/confirm/${orderId}`,
method: 'POST'
});
},
// 申请退款
async applyRefund(orderId, data) {
return request({
url: `/api/order/refund/${orderId}`,
method: 'POST',
data
});
},
// 获取订单状态
async getOrderStatus(orderId) {
return request({
url: `/api/order/status/${orderId}`,
method: 'GET'
});
},
// 支付订单
async payOrder(orderId, paymentData) {
return request({
url: `/api/order/pay/${orderId}`,
method: 'POST',
data: paymentData
});
},
// 订单统计
async getOrderStats() {
return request({
url: '/api/order/stats',
method: 'GET'
});
}
};3. 订单列表页面
<template>
<view class="order-list">
<view class="tab-bar">
<uni-segmented-control
:values="['全部', '待支付', '待发货', '待收货', '已完成', '已取消']"
:current="activeTab"
@clickItem="handleTabChange"
style-type="button"
active-color="#FF6600"
/>
</view>
<uni-refresher
v-model="refreshing"
@refresh="onRefresh"
:contentdown="{ content: '下拉刷新' }"
:contentover="{ content: '释放刷新' }"
:contentrefresh="{ content: '刷新中...' }"
>
<view v-if="loading" class="loading-state">
<uni-icons type="spinner" size="30" color="#FF6600" animation="spin" />
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="orders.length > 0" class="order-items">
<view
v-for="(order, index) in orders"
:key="order.id"
class="order-item"
>
<view class="order-header">
<text class="order-time">{{ formatDate(order.createdAt) }}</text>
<text :class="['order-status', order.status]"
>{{ getStatusText(order.status) }}</text>
</view>
<view class="order-products">
<view
v-for="(product, pIndex) in order.products"
:key="pIndex"
class="product-item"
>
<image :src="product.image" mode="aspectFill" class="product-image" />
<view class="product-info">
<text class="product-title">{{ product.title }}</text>
<text class="product-spec">{{ product.spec }}</text>
<view class="product-price-count">
<text class="product-price">¥{{ product.price.toFixed(2) }}</text>
<text class="product-count">x{{ product.quantity }}</text>
</view>
</view>
</view>
</view>
<view class="order-footer">
<view class="order-amount">
<text class="label">共{{ order.products.length }}件商品</text>
<text class="amount">合计:¥{{ order.amount.total.toFixed(2) }}</text>
</view>
<view class="order-actions">
<button
v-if="order.status === 'pending_payment'"
class="action-btn secondary"
@click="cancelOrder(order.id)"
>
取消订单
</button>
<button
v-if="order.status === 'pending_payment'"
class="action-btn primary"
@click="payOrder(order.id, order.amount.total)"
>
去支付
</button>
<button
v-if="order.status === 'pending_delivery'"
class="action-btn primary"
@click="viewLogistics(order.id)"
>
查看物流
</button>
<button
v-if="order.status === 'pending_receipt'"
class="action-btn primary"
@click="confirmReceipt(order.id)"
>
确认收货
</button>
<button
v-if="order.status === 'completed'"
class="action-btn secondary"
@click="viewOrderDetail(order.id)"
>
查看详情
</button>
<button
v-if="order.status === 'completed'"
class="action-btn primary"
@click="reviewOrder(order.id)"
>
去评价
</button>
</view>
</view>
</view>
<view v-if="hasMore" class="load-more" @click="loadMore">
<text>加载更多</text>
</view>
</view>
<view v-else class="empty-state">
<uni-icons type="document" size="60" color="#CCCCCC" />
<text class="empty-text">暂无订单</text>
</view>
</uni-refresher>
</view>
</template>
<script>
import orderApi from '@/api/order';
export default {
data() {
return {
activeTab: 0,
orders: [],
loading: false,
refreshing: false,
page: 1,
pageSize: 10,
hasMore: true,
statusMap: {
0: '',
1: 'pending_payment',
2: 'pending_delivery',
3: 'pending_receipt',
4: 'completed',
5: 'cancelled'
}
};
},
onLoad() {
this.loadOrders();
},
methods: {
loadOrders() {
this.loading = true;
const status = this.statusMap[this.activeTab];
orderApi.getOrderList({
status,
page: this.page,
pageSize: this.pageSize
}).then(res => {
if (res.success) {
const newOrders = res.data.orders || [];
if (this.page === 1) {
this.orders = newOrders;
} else {
this.orders = [...this.orders, ...newOrders];
}
this.hasMore = newOrders.length === this.pageSize;
}
}).catch(error => {
console.error('加载订单失败:', error);
}).finally(() => {
this.loading = false;
this.refreshing = false;
});
},
handleTabChange(e) {
this.activeTab = e.current;
this.page = 1;
this.orders = [];
this.hasMore = true;
this.loadOrders();
},
onRefresh() {
this.refreshing = true;
this.page = 1;
this.loadOrders();
},
loadMore() {
if (this.loading || !this.hasMore) return;
this.page++;
this.loadOrders();
},
getStatusText(status) {
const statusTextMap = {
'pending_payment': '待支付',
'pending_delivery': '待发货',
'pending_receipt': '待收货',
'completed': '已完成',
'cancelled': '已取消'
};
return statusTextMap[status] || status;
},
formatDate(dateString) {
const date = new Date(dateString);
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
},
cancelOrder(orderId) {
uni.showModal({
title: '确认取消',
content: '确定要取消该订单吗?',
success: (res) => {
if (res.confirm) {
orderApi.cancelOrder(orderId).then(res => {
if (res.success) {
uni.showToast({
title: '订单已取消',
icon: 'success'
});
this.onRefresh();
}
});
}
}
});
},
payOrder(orderId, amount) {
uni.navigateTo({
url: `/pages/payment/index?orderId=${orderId}&amount=${amount}`
});
},
viewLogistics(orderId) {
uni.navigateTo({
url: `/pages/logistics/index?orderId=${orderId}`
});
},
confirmReceipt(orderId) {
uni.showModal({
title: '确认收货',
content: '确定已收到商品吗?',
success: (res) => {
if (res.confirm) {
orderApi.confirmReceipt(orderId).then(res => {
if (res.success) {
uni.showToast({
title: '收货成功',
icon: 'success'
});
this.onRefresh();
}
});
}
}
});
},
viewOrderDetail(orderId) {
uni.navigateTo({
url: `/pages/order/detail?id=${orderId}`
});
},
reviewOrder(orderId) {
uni.navigateTo({
url: `/pages/order/review?id=${orderId}`
});
}
}
};
</script>
<style scoped>
.order-list {
min-height: 100vh;
background-color: #F5F5F5;
}
.tab-bar {
background-color: #FFFFFF;
padding: 10rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
position: sticky;
top: 0;
z-index: 10;
}
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #666;
}
.order-items {
padding: 10rpx;
}
.order-item {
background-color: #FFFFFF;
border-radius: 10rpx;
margin-bottom: 20rpx;
overflow: hidden;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #F0F0F0;
}
.order-time {
font-size: 24rpx;
color: #999;
}
.order-status {
font-size: 26rpx;
font-weight: bold;
}
.order-status.pending_payment {
color: #FF9800;
}
.order-status.pending_delivery {
color: #2196F3;
}
.order-status.pending_receipt {
color: #4CAF50;
}
.order-status.completed {
color: #9E9E9E;
}
.order-status.cancelled {
color: #9E9E9E;
}
.order-products {
padding: 20rpx;
}
.product-item {
display: flex;
margin-bottom: 20rpx;
}
.product-item:last-child {
margin-bottom: 0;
}
.product-image {
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
}
.product-info {
flex: 1;
margin-left: 15rpx;
}
.product-title {
font-size: 26rpx;
color: #333;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.product-spec {
font-size: 24rpx;
color: #999;
margin-bottom: 15rpx;
}
.product-price-count {
display: flex;
justify-content: space-between;
align-items: center;
}
.product-price {
font-size: 26rpx;
color: #333;
font-weight: bold;
}
.product-count {
font-size: 24rpx;
color: #666;
}
.order-footer {
padding: 20rpx;
border-top: 1rpx solid #F0F0F0;
}
.order-amount {
display: flex;
justify-content: flex-end;
align-items: center;
margin-bottom: 20rpx;
font-size: 26rpx;
}
.order-amount .label {
color: #666;
margin-right: 10rpx;
}
.order-amount .amount {
color: #333;
font-weight: bold;
}
.order-actions {
display: flex;
justify-content: flex-end;
gap: 10rpx;
}
.action-btn {
padding: 10rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
min-width: 120rpx;
}
.action-btn.primary {
background-color: #FF6600;
color: #FFFFFF;
}
.action-btn.secondary {
background-color: #FFFFFF;
color: #666;
border: 1rpx solid #E0E0E0;
}
.load-more {
text-align: center;
padding: 30rpx 0;
color: #666;
font-size: 28rpx;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 150rpx 0;
color: #999;
}
.empty-text {
margin-top: 20rpx;
font-size: 28rpx;
}
</style>4. 订单详情页面
<template>
<view class="order-detail">
<view class="section">
<view class="order-status">
<text class="status-icon">{{ getStatusIcon(order.status) }}</text>
<text class="status-text">{{ getStatusText(order.status) }}</text>
<text class="status-desc">{{ getStatusDesc(order.status) }}</text>
</view>
</view>
<view class="section">
<text class="section-title">收货信息</text>
<view class="address-info">
<uni-icons type="location" size="24" color="#FF6600" />
<view class="address-content">
<text class="consignee">{{ order.address.consignee }} {{ order.address.phone }}</text>
<text class="address-detail">{{ order.address.province }}{{ order.address.city }}{{ order.address.district }}{{ order.address.detail }}</text>
</view>
</view>
</view>
<view class="section">
<text class="section-title">商品信息</text>
<view class="product-list">
<view
v-for="(product, index) in order.products"
:key="index"
class="product-item"
>
<image :src="product.image" mode="aspectFill" class="product-image" />
<view class="product-info">
<text class="product-title">{{ product.title }}</text>
<text class="product-spec">{{ product.spec }}</text>
<view class="product-price-count">
<text class="product-price">¥{{ product.price.toFixed(2) }}</text>
<text class="product-count">x{{ product.quantity }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="section">
<text class="section-title">订单信息</text>
<view class="order-info">
<view class="info-item">
<text class="label">订单编号</text>
<text class="value">{{ order.orderNo }}</text>
</view>
<view class="info-item">
<text class="label">创建时间</text>
<text class="value">{{ formatDate(order.createdAt) }}</text>
</view>
<view class="info-item" v-if="order.paymentTime">
<text class="label">支付时间</text>
<text class="value">{{ formatDate(order.paymentTime) }}</text>
</view>
<view class="info-item" v-if="order.deliveryTime">
<text class="label">发货时间</text>
<text class="value">{{ formatDate(order.deliveryTime) }}</text>
</view>
<view class="info-item" v-if="order.completeTime">
<text class="label">完成时间</text>
<text class="value">{{ formatDate(order.completeTime) }}</text>
</view>
<view class="info-item">
<text class="label">支付方式</text>
<text class="value">{{ getPaymentText(order.paymentMethod) }}</text>
</view>
<view class="info-item" v-if="order.remark">
<text class="label">订单备注</text>
<text class="value">{{ order.remark }}</text>
</view>
</view>
</view>
<view class="section">
<text class="section-title">费用明细</text>
<view class="cost-detail">
<view class="cost-item">
<text class="label">商品金额</text>
<text class="value">¥{{ order.amount.subtotal.toFixed(2) }}</text>
</view>
<view class="cost-item">
<text class="label">运费</text>
<text class="value">¥{{ order.amount.shippingFee.toFixed(2) }}</text>
</view>
<view class="cost-item total">
<text class="label">订单总额</text>
<text class="value">¥{{ order.amount.total.toFixed(2) }}</text>
</view>
</view>
</view>
<view class="bottom-bar" v-if="showActions">
<view class="actions">
<button
v-if="order.status === 'pending_payment'"
class="action-btn secondary"
@click="cancelOrder"
>
取消订单
</button>
<button
v-if="order.status === 'pending_payment'"
class="action-btn primary"
@click="payOrder"
>
去支付
</button>
<button
v-if="order.status === 'pending_delivery'"
class="action-btn primary"
@click="viewLogistics"
>
查看物流
</button>
<button
v-if="order.status === 'pending_receipt'"
class="action-btn primary"
@click="confirmReceipt"
>
确认收货
</button>
<button
v-if="order.status === 'completed'"
class="action-btn primary"
@click="reviewOrder"
>
去评价
</button>
</view>
</view>
</view>
</template>
<script>
import orderApi from '@/api/order';
export default {
data() {
return {
order: {},
loading: true
};
},
computed: {
showActions() {
const status = this.order.status;
return ['pending_payment', 'pending_delivery', 'pending_receipt', 'completed'].includes(status);
}
},
onLoad(options) {
if (options.id) {
this.loadOrderDetail(options.id);
}
},
methods: {
loadOrderDetail(orderId) {
this.loading = true;
orderApi.getOrderDetail(orderId).then(res => {
if (res.success) {
this.order = res.data;
} else {
uni.showToast({
title: '加载订单失败',
icon: 'none'
});
}
}).catch(error => {
console.error('加载订单详情失败:', error);
}).finally(() => {
this.loading = false;
});
},
getStatusText(status) {
const statusTextMap = {
'pending_payment': '待支付',
'pending_delivery': '待发货',
'pending_receipt': '待收货',
'completed': '已完成',
'cancelled': '已取消'
};
return statusTextMap[status] || status;
},
getStatusIcon(status) {
const iconMap = {
'pending_payment': 'time',
'pending_delivery': 'truck',
'pending_receipt': 'package',
'completed': 'checkmarkcircle',
'cancelled': 'closecircle'
};
return iconMap[status] || 'document';
},
getStatusDesc(status) {
const descMap = {
'pending_payment': '请尽快完成支付',
'pending_delivery': '商家正在处理您的订单',
'pending_receipt': '商品正在配送中',
'completed': '交易已完成',
'cancelled': '订单已取消'
};
return descMap[status] || '';
},
getPaymentText(method) {
const paymentMap = {
'wechat': '微信支付',
'alipay': '支付宝',
'bank': '银行卡支付'
};
return paymentMap[method] || method;
},
formatDate(dateString) {
const date = new Date(dateString);
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
},
cancelOrder() {
uni.showModal({
title: '确认取消',
content: '确定要取消该订单吗?',
success: (res) => {
if (res.confirm) {
orderApi.cancelOrder(this.order.id).then(res => {
if (res.success) {
uni.showToast({
title: '订单已取消',
icon: 'success'
});
this.loadOrderDetail(this.order.id);
}
});
}
}
});
},
payOrder() {
uni.navigateTo({
url: `/pages/payment/index?orderId=${this.order.id}&amount=${this.order.amount.total}`
});
},
viewLogistics() {
uni.navigateTo({
url: `/pages/logistics/index?orderId=${this.order.id}`
});
},
confirmReceipt() {
uni.showModal({
title: '确认收货',
content: '确定已收到商品吗?',
success: (res) => {
if (res.confirm) {
orderApi.confirmReceipt(this.order.id).then(res => {
if (res.success) {
uni.showToast({
title: '收货成功',
icon: 'success'
});
this.loadOrderDetail(this.order.id);
}
});
}
}
});
},
reviewOrder() {
uni.navigateTo({
url: `/pages/order/review?id=${this.order.id}`
});
}
}
};
</script>
<style scoped>
.order-detail {
min-height: 100vh;
background-color: #F5F5F5;
}
.section {
background-color: #FFFFFF;
margin-bottom: 10rpx;
padding: 20rpx;
}
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.order-status {
display: flex;
flex-direction: column;
align-items: center;
padding: 30rpx 0;
background-color: #FFF3E0;
border-radius: 10rpx;
margin-bottom: 20rpx;
}
.status-icon {
font-size: 60rpx;
color: #FF6600;
margin-bottom: 15rpx;
}
.status-text {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.status-desc {
font-size: 24rpx;
color: #666;
}
.address-info {
display: flex;
align-items: flex-start;
}
.address-content {
flex: 1;
margin-left: 15rpx;
}
.consignee {
font-size: 26rpx;
color: #333;
margin-bottom: 10rpx;
display: block;
}
.address-detail {
font-size: 24rpx;
color: #666;
line-height: 1.4;
}
.product-list {
padding: 10rpx 0;
}
.product-item {
display: flex;
margin-bottom: 20rpx;
}
.product-item:last-child {
margin-bottom: 0;
}
.product-image {
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
}
.product-info {
flex: 1;
margin-left: 15rpx;
}
.product-title {
font-size: 26rpx;
color: #333;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.product-spec {
font-size: 24rpx;
color: #999;
margin-bottom: 15rpx;
}
.product-price-count {
display: flex;
justify-content: space-between;
align-items: center;
}
.product-price {
font-size: 26rpx;
color: #333;
font-weight: bold;
}
.product-count {
font-size: 24rpx;
color: #666;
}
.order-info {
padding: 10rpx 0;
}
.info-item {
display: flex;
margin-bottom: 15rpx;
font-size: 26rpx;
}
.info-item .label {
width: 120rpx;
color: #666;
}
.info-item .value {
flex: 1;
color: #333;
}
.cost-detail {
padding: 10rpx 0;
}
.cost-item {
display: flex;
justify-content: space-between;
margin-bottom: 15rpx;
font-size: 26rpx;
}
.cost-item.total {
font-weight: bold;
margin-top: 10rpx;
padding-top: 15rpx;
border-top: 1rpx solid #E0E0E0;
}
.cost-item .label {
color: #666;
}
.cost-item .value {
color: #333;
}
.cost-item.total .value {
color: #FF6600;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #FFFFFF;
border-top: 1rpx solid #E0E0E0;
padding: 20rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.actions {
display: flex;
justify-content: flex-end;
gap: 10rpx;
}
.action-btn {
padding: 10rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
min-width: 120rpx;
}
.action-btn.primary {
background-color: #FF6600;
color: #FFFFFF;
}
.action-btn.secondary {
background-color: #FFFFFF;
color: #666;
border: 1rpx solid #E0E0E0;
}
</style>实用技巧
1. 订单状态管理
- 状态枚举:使用枚举定义订单状态,确保状态一致性
- 状态流转:定义清晰的状态流转规则,避免状态混乱
- 状态监听:使用 WebSocket 或轮询实时监听状态变化
- 状态持久化:确保状态变更持久化到后端数据库
2. 订单支付安全
- 签名验证:确保支付请求和回调的签名验证
- 订单金额校验:前端显示金额与后端计算金额一致性校验
- 重复支付防护:防止用户重复支付同一订单
- 支付超时处理:设置合理的支付超时时间
3. 订单系统性能优化
- 分页加载:订单列表使用分页加载,避免一次性加载过多数据
- 数据缓存:缓存订单列表和详情数据,减少重复请求
- 异步处理:订单创建、支付等操作使用异步处理
- 批量操作:支持批量订单操作,提高处理效率
4. 订单异常处理
- 网络异常:处理网络不稳定导致的订单操作失败
- 支付异常:处理支付失败、退款等异常情况
- 物流异常:处理物流信息异常、包裹丢失等情况
- 系统异常:处理系统崩溃、数据丢失等极端情况
5. 用户体验优化
- 订单状态清晰:使用直观的状态名称和图标
- 操作引导:为用户提供清晰的操作引导
- 进度追踪:提供订单处理进度的实时追踪
- 通知提醒:重要状态变更时及时通知用户
总结
通过本教程的学习,你已经掌握了 uni-app 订单系统的完整实现方法,包括:
订单系统架构设计:了解了订单系统的前端组件、后端服务、数据层、支付集成等核心组成部分
订单生命周期管理:掌握了订单从创建到完成的完整生命周期管理
订单状态管理:学会了订单状态的定义、更新、监听和回滚
订单支付流程:掌握了支付方式集成、支付请求、支付回调、异常处理等
订单管理功能:实现了订单列表、订单详情、订单操作等核心功能
性能优化和用户体验:应用了多种性能优化策略和用户体验提升技巧
订单系统是电商应用的核心功能之一,它不仅关系到用户的购物体验,还直接影响到商家的运营效率。在实际项目中,你可以根据具体业务需求对本教程中的实现进行扩展和优化,构建更加完善的订单系统。
学习目标
- 掌握 uni-app 订单系统的完整实现方法
- 理解订单系统的架构设计和数据流程
- 学会订单状态管理和支付流程集成
- 掌握订单系统的性能优化和异常处理
- 实现订单创建、支付、物流、售后等完整功能
- 构建安全、高效、用户友好的订单系统
通过本教程的学习,你已经具备了开发高质量订单系统的能力,可以在实际项目中灵活应用这些知识,为用户提供流畅的购物体验。