feat: 新增退货功能,订单列表增加退货状态(9)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -54,6 +54,12 @@
|
||||
"navigationBarTitleText": "订单查询"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/return",
|
||||
"style": {
|
||||
"navigationBarTitleText": "退货"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/stock/list",
|
||||
"style": {
|
||||
|
||||
@@ -74,6 +74,10 @@
|
||||
<Icon name="search" :size="48" color="#fff" />
|
||||
<text class="menu-card-title">订单查询</text>
|
||||
</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()">
|
||||
<Icon name="stock" :size="48" color="#fff" />
|
||||
<text class="menu-card-title">库存管理</text>
|
||||
|
||||
@@ -208,7 +208,8 @@ export default {
|
||||
1: 'status-success',
|
||||
2: 'status-cancel',
|
||||
3: 'status-refunding',
|
||||
4: 'status-refunded'
|
||||
4: 'status-refunded',
|
||||
9: 'status-returning'
|
||||
}
|
||||
return map[status] || ''
|
||||
},
|
||||
@@ -218,7 +219,8 @@ export default {
|
||||
1: '已完成',
|
||||
2: '已取消',
|
||||
3: '退款中',
|
||||
4: '已退款'
|
||||
4: '已退款',
|
||||
9: '退货中'
|
||||
}
|
||||
return map[status] || '未知'
|
||||
},
|
||||
@@ -367,6 +369,11 @@ export default {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.status-returning {
|
||||
background: linear-gradient(135deg, #fa8c16 0%, #ffc069 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 卡片主体 */
|
||||
.card-body {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
532
src/pages/order/return.vue
Normal file
532
src/pages/order/return.vue
Normal 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>
|
||||
@@ -7,7 +7,7 @@
|
||||
<input
|
||||
class="search-input"
|
||||
v-model="keyword"
|
||||
placeholder="输入客户姓名搜索"
|
||||
placeholder="输入客户姓名或手机号搜索"
|
||||
placeholder-class="placeholder"
|
||||
@input="searchCustomers"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user