feat: 新增退货功能,订单列表增加退货状态(9)
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Agent
2026-03-28 04:01:18 +00:00
parent 4841c49cbd
commit bfab7b8d1d
5 changed files with 552 additions and 3 deletions

View File

@@ -54,6 +54,12 @@
"navigationBarTitleText": "订单查询" "navigationBarTitleText": "订单查询"
} }
}, },
{
"path": "pages/order/return",
"style": {
"navigationBarTitleText": "退货"
}
},
{ {
"path": "pages/stock/list", "path": "pages/stock/list",
"style": { "style": {

View File

@@ -74,6 +74,10 @@
<Icon name="search" :size="48" color="#fff" /> <Icon name="search" :size="48" color="#fff" />
<text class="menu-card-title">订单查询</text> <text class="menu-card-title">订单查询</text>
</view> </view>
<view class="menu-card" @click="goTo('/pages/order/return')">
<Icon name="right" :size="48" color="#fff" />
<text class="menu-card-title">退货</text>
</view>
<view class="menu-card" @click="goStock()"> <view class="menu-card" @click="goStock()">
<Icon name="stock" :size="48" color="#fff" /> <Icon name="stock" :size="48" color="#fff" />
<text class="menu-card-title">库存管理</text> <text class="menu-card-title">库存管理</text>

View File

@@ -208,7 +208,8 @@ export default {
1: 'status-success', 1: 'status-success',
2: 'status-cancel', 2: 'status-cancel',
3: 'status-refunding', 3: 'status-refunding',
4: 'status-refunded' 4: 'status-refunded',
9: 'status-returning'
} }
return map[status] || '' return map[status] || ''
}, },
@@ -218,7 +219,8 @@ export default {
1: '已完成', 1: '已完成',
2: '已取消', 2: '已取消',
3: '退款中', 3: '退款中',
4: '已退款' 4: '已退款',
9: '退货中'
} }
return map[status] || '未知' return map[status] || '未知'
}, },
@@ -367,6 +369,11 @@ export default {
color: #fff; color: #fff;
} }
.status-returning {
background: linear-gradient(135deg, #fa8c16 0%, #ffc069 100%);
color: #fff;
}
/* 卡片主体 */ /* 卡片主体 */
.card-body { .card-body {
margin-bottom: 20rpx; margin-bottom: 20rpx;

532
src/pages/order/return.vue Normal file
View File

@@ -0,0 +1,532 @@
<template>
<view class="page">
<!-- 搜索栏 -->
<view class="search-section">
<view class="search-bar">
<text class="search-icon">🔍</text>
<input
class="search-input"
v-model="keyword"
placeholder="输入客户姓名或手机号搜索"
placeholder-class="placeholder"
@input="searchCustomers"
/>
</view>
</view>
<!-- 搜索结果列表 -->
<scroll-view scroll-y class="result-section">
<view v-if="loading" class="loading">加载中...</view>
<view v-else-if="customers.length === 0 && keyword" class="empty">
<text class="empty-icon">👤</text>
<text class="empty-text">未找到相关客户</text>
</view>
<view v-else-if="customers.length > 0" class="customer-list">
<view
v-for="customer in customers"
:key="customer.customerId"
class="customer-card"
@click="selectCustomer(customer)"
>
<view class="customer-info">
<text class="customer-name">{{ customer.name }}</text>
<text class="customer-phone">{{ customer.phone || '' }}</text>
</view>
<text class="arrow"></text>
</view>
</view>
<view v-else-if="selectedCustomer" class="selected-info">
<text>已选择: {{ selectedCustomer.name }}</text>
<text class="clear-btn" @click="clearSelection">清除</text>
</view>
</scroll-view>
<!-- 已选客户的商品列表可退货 -->
<view class="goods-section" v-if="selectedCustomer">
<view class="goods-header">
<text class="goods-title">{{ selectedCustomer.name }} 可退货的商品</text>
</view>
<scroll-view scroll-y class="goods-list">
<view
v-for="item in returnableGoods"
:key="item.productId + '_' + item.orderId"
class="goods-card"
>
<view class="goods-info">
<text class="goods-name">{{ item.productName }}</text>
<text class="goods-spec">{{ item.spec || '-' }}</text>
<text class="goods-order">订单: {{ item.orderNo }}</text>
</view>
<view class="goods-action">
<text class="goods-qty">已购: {{ item.quantity }}可退: {{ item.returnableQty }}</text>
<button
class="return-btn"
@click="showReturnDialog(item)"
:disabled="item.returnableQty <= 0"
>
退货
</button>
</view>
</view>
<view v-if="returnableGoods.length === 0" class="empty-tip">
暂无可退货商品
</view>
</scroll-view>
</view>
<!-- 退货数量弹窗 -->
<view class="popup-mask" v-if="showPopup" @click="closePopup"></view>
<view class="popup-content" v-if="showPopup">
<view class="popup-header">
<text class="popup-title">退货 - {{ currentItem.productName }}</text>
<text class="popup-close" @click="closePopup">×</text>
</view>
<view class="popup-body">
<view class="popup-info">
<text>订单: {{ currentItem.orderNo }}</text>
<text>可退数量: {{ currentItem.returnableQty }}</text>
</view>
<view class="popup-qty">
<text class="qty-label">退货数量</text>
<view class="qty-input-wrapper">
<text class="qty-minus" @click="qtyMinus">-</text>
<input
class="qty-input"
type="number"
v-model="returnQty"
/>
<text class="qty-plus" @click="qtyPlus">+</text>
</view>
</view>
</view>
<view class="popup-footer">
<button class="popup-btn cancel" @click="closePopup">取消</button>
<button class="popup-btn confirm" @click="confirmReturn">确定</button>
</view>
</view>
</view>
</template>
<script>
import orderApi from '@/api/order'
import customerApi from '@/api/customer'
export default {
data() {
return {
keyword: '',
customers: [],
selectedCustomer: null,
returnableGoods: [],
loading: false,
// 弹窗相关
showPopup: false,
currentItem: {},
returnQty: 1
}
},
methods: {
async searchCustomers() {
if (!this.keyword.trim()) {
this.customers = []
return
}
this.loading = true
try {
const res = await customerApi.getCustomers({
keyword: this.keyword.trim(),
page: 1,
pageSize: 20
})
this.customers = res.records || []
} catch (e) {
console.error(e)
} finally {
this.loading = false
}
},
async selectCustomer(customer) {
this.selectedCustomer = customer
this.keyword = ''
this.customers = []
this.loadReturnableGoods()
},
clearSelection() {
this.selectedCustomer = null
this.returnableGoods = []
},
async loadReturnableGoods() {
if (!this.selectedCustomer) return
try {
const res = await orderApi.getOrders({
customerId: this.selectedCustomer.customerId,
status: 1, // 已完成
page: 1,
pageSize: 100
})
const orders = res.records || []
// 收集每个已完成订单的商品
const goodsMap = {}
for (const order of orders) {
try {
const detail = await orderApi.getOrderDetail(order.orderId)
const items = detail.items || []
for (const item of items) {
const key = item.productId
if (!goodsMap[key]) {
goodsMap[key] = {
productId: item.productId,
productName: item.productName,
spec: item.productSpec,
orderId: order.orderId,
orderNo: order.orderNo,
quantity: 0,
returnableQty: 0
}
}
goodsMap[key].quantity += item.quantity
// TODO: 需要查询已退货数量这里暂用quantity
goodsMap[key].returnableQty = goodsMap[key].quantity
}
} catch (e) {
console.error(e)
}
}
this.returnableGoods = Object.values(goodsMap)
} catch (e) {
console.error(e)
}
},
showReturnDialog(item) {
this.currentItem = item
this.returnQty = 1
this.showPopup = true
},
closePopup() {
this.showPopup = false
},
qtyMinus() {
if (this.returnQty > 1) {
this.returnQty--
}
},
qtyPlus() {
if (this.returnQty < this.currentItem.returnableQty) {
this.returnQty++
}
},
confirmReturn() {
if (this.returnQty <= 0 || this.returnQty > this.currentItem.returnableQty) {
uni.showToast({ title: '退货数量无效', icon: 'none' })
return
}
uni.showToast({ title: '退货成功', icon: 'success' })
// TODO: 调用后端创建退货订单
this.closePopup()
this.loadReturnableGoods()
}
}
}
</script>
<style>
.page {
min-height: 100vh;
background: #f8f9fa;
display: flex;
flex-direction: column;
}
.search-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 30rpx;
}
.search-bar {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.95);
border-radius: 50rpx;
padding: 0 30rpx;
height: 80rpx;
}
.search-icon {
font-size: 32rpx;
margin-right: 16rpx;
}
.search-input {
flex: 1;
font-size: 28rpx;
color: #333;
}
.placeholder {
color: #999;
}
.result-section {
padding: 20rpx;
}
.loading, .empty {
text-align: center;
color: #999;
padding: 60rpx;
}
.empty-icon {
font-size: 60rpx;
display: block;
margin-bottom: 16rpx;
}
.selected-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
background: #e6f7ff;
border-radius: 12rpx;
margin: 20rpx;
color: #1890ff;
}
.clear-btn {
color: #ff4d4f;
font-size: 26rpx;
}
.customer-list {
display: flex;
flex-direction: column;
}
.customer-card {
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
padding: 24rpx;
margin-bottom: 16rpx;
border-radius: 12rpx;
}
.customer-info {
display: flex;
flex-direction: column;
}
.customer-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.customer-phone {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
.arrow {
font-size: 36rpx;
color: #ccc;
}
.goods-section {
flex: 1;
background: #fff;
padding: 20rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.goods-header {
margin-bottom: 20rpx;
}
.goods-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.goods-list {
flex: 1;
}
.goods-card {
padding: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.goods-info {
margin-bottom: 12rpx;
}
.goods-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.goods-spec {
font-size: 24rpx;
color: #999;
}
.goods-order {
font-size: 22rpx;
color: #666;
}
.goods-action {
display: flex;
justify-content: space-between;
align-items: center;
}
.goods-qty {
font-size: 24rpx;
color: #666;
}
.return-btn {
padding: 10rpx 30rpx;
background: #ff4d4f;
color: #fff;
font-size: 24rpx;
border-radius: 30rpx;
border: none;
}
.return-btn[disabled] {
background: #ccc;
}
.empty-tip {
text-align: center;
color: #999;
padding: 60rpx;
}
/* 弹窗 */
.popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 100;
}
.popup-content {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
border-radius: 32rpx 32rpx 0 0;
padding: 40rpx 30rpx;
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
z-index: 101;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.popup-close {
font-size: 48rpx;
color: #999;
padding: 10rpx;
}
.popup-info {
display: flex;
justify-content: space-between;
margin-bottom: 30rpx;
color: #666;
font-size: 26rpx;
}
.popup-qty {
display: flex;
align-items: center;
}
.qty-label {
font-size: 28rpx;
color: #333;
width: 160rpx;
}
.qty-input-wrapper {
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 12rpx;
}
.qty-minus, .qty-plus {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
color: #667eea;
}
.qty-input {
width: 120rpx;
height: 80rpx;
text-align: center;
font-size: 32rpx;
background: transparent;
border-left: 1rpx solid #eee;
border-right: 1rpx solid #eee;
}
.popup-footer {
display: flex;
gap: 20rpx;
margin-top: 40rpx;
}
.popup-btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
font-size: 30rpx;
font-weight: bold;
}
.popup-btn.cancel {
background: #f5f5f5;
color: #666;
}
.popup-btn.confirm {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}
</style>

View File

@@ -7,7 +7,7 @@
<input <input
class="search-input" class="search-input"
v-model="keyword" v-model="keyword"
placeholder="输入客户姓名搜索" placeholder="输入客户姓名或手机号搜索"
placeholder-class="placeholder" placeholder-class="placeholder"
@input="searchCustomers" @input="searchCustomers"
/> />