Files
todo-frontend/src/pages/order/search.vue
Agent bfab7b8d1d
All checks were successful
continuous-integration/drone/push Build is passing
feat: 新增退货功能,订单列表增加退货状态(9)
2026-03-28 04:01:18 +00:00

454 lines
9.5 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="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="orders-section" v-if="selectedCustomer">
<view class="orders-header">
<text class="orders-title">{{ selectedCustomer.name }} 的订单</text>
</view>
<scroll-view scroll-y class="orders-list">
<view
v-for="order in orders"
:key="order.orderId"
class="order-card"
:class="{ 'selected': merging && order.status === 1 && selectedOrders.includes(order.orderId) }"
@click="toggleOrderSelection(order)"
>
<view class="card-checkbox" v-if="merging && order.status === 1">
<text>{{ selectedOrders.includes(order.orderId) ? '☑' : '☐' }}</text>
</view>
<view class="card-content" :class="{ 'with-checkbox': merging && order.status === 1 }">
<view class="card-header">
<text class="order-no">{{ order.orderNo }}</text>
<text class="order-status" :class="getStatusClass(order.status)">
{{ getStatusText(order.status) }}
</text>
</view>
<view class="card-body">
<text class="order-amount">实付: ¥{{ order.actualAmount }}</text>
<text class="order-time">{{ formatTime(order.createdAt) }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 合并订单按钮 -->
<view class="bottom-bar" v-if="selectedCustomer && orders.length > 0">
<button
class="merge-btn"
@click="startMerge"
v-if="!merging"
>
合并订单
</button>
<view class="merge-actions" v-else>
<button class="cancel-btn" @click="cancelMerge">取消</button>
<button
class="confirm-btn"
@click="confirmMerge"
:disabled="selectedOrders.length < 2"
>
确定 ({{ selectedOrders.length }})
</button>
</view>
</view>
</view>
</template>
<script>
import orderApi from '@/api/order'
import customerApi from '@/api/customer'
export default {
data() {
return {
keyword: '',
customers: [],
selectedCustomer: null,
orders: [],
loading: false,
// 合并订单相关
merging: false,
selectedOrders: []
}
},
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.loadOrders()
},
clearSelection() {
this.selectedCustomer = null
this.orders = []
this.merging = false
this.selectedOrders = []
},
toggleOrderSelection(order) {
if (!this.merging || order.status !== 1) return
const index = this.selectedOrders.indexOf(order.orderId)
if (index > -1) {
this.selectedOrders.splice(index, 1)
} else {
this.selectedOrders.push(order.orderId)
}
},
startMerge() {
this.merging = true
this.selectedOrders = []
},
cancelMerge() {
this.merging = false
this.selectedOrders = []
},
confirmMerge() {
if (this.selectedOrders.length < 2) {
uni.showToast({ title: '请至少选择2个订单', icon: 'none' })
return
}
uni.showToast({ title: '选中 ' + this.selectedOrders.length + ' 个订单', icon: 'success' })
// TODO: 后端合并订单逻辑
},
async loadOrders() {
if (!this.selectedCustomer) return
try {
const res = await orderApi.getOrders({
customerId: this.selectedCustomer.customerId,
page: 1,
pageSize: 50
})
this.orders = res.records || []
} catch (e) {
console.error(e)
}
},
getStatusClass(status) {
const map = {
1: 'status-success',
2: 'status-cancel'
}
return map[status] || ''
},
getStatusText(status) {
const map = {
0: '未完成',
1: '已完成',
2: '已取消'
}
return map[status] || '未知'
},
formatTime(time) {
if (!time) return ''
return time.substring(0, 16).replace('T', ' ')
},
viewDetail(order) {
uni.showModal({
title: '订单详情',
content: `订单号: ${order.orderNo}\n原价: ¥${order.totalAmount}\n优惠: ¥${order.discountAmount}\n实付: ¥${order.actualAmount}\n状态: ${this.getStatusText(order.status)}`,
showCancel: false
})
}
}
}
</script>
<style>
.page {
min-height: 100vh;
background: #f8f9fa;
}
.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;
}
.orders-section {
background: #fff;
padding: 20rpx;
flex: 1;
overflow-y: auto;
}
.orders-header {
margin-bottom: 20rpx;
}
.orders-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.orders-list {
flex: 1;
}
.order-card {
padding: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
display: flex;
align-items: center;
}
.order-card.selected {
background: #e6f7ff;
}
.card-checkbox {
margin-right: 16rpx;
font-size: 36rpx;
}
.card-content.with-checkbox {
flex: 1;
}
.card-content {
flex: 1;
}
.card-header {
display: flex;
justify-content: space-between;
margin-bottom: 12rpx;
}
.order-no {
font-size: 26rpx;
color: #333;
}
.order-status {
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 16rpx;
}
.status-success {
background: #e6f7e6;
color: #52c41a;
}
.status-cancel {
background: #fff1f0;
color: #ff4d4f;
}
.card-body {
display: flex;
justify-content: space-between;
}
.order-amount {
font-size: 28rpx;
font-weight: bold;
color: #ff4d4f;
}
.order-time {
font-size: 24rpx;
color: #999;
}
.bottom-bar {
padding: 20rpx;
background: #fff;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.merge-btn {
width: 100%;
height: 88rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
border: none;
border-radius: 44rpx;
font-size: 30rpx;
font-weight: bold;
}
.merge-actions {
display: flex;
gap: 20rpx;
}
.cancel-btn, .confirm-btn {
flex: 1;
height: 88rpx;
border-radius: 44rpx;
font-size: 30rpx;
font-weight: bold;
border: none;
}
.cancel-btn {
background: #f5f5f5;
color: #666;
}
.confirm-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}
.confirm-btn[disabled] {
background: #ccc;
}
</style>