2025-07-19 20:00:08 +08:00

438 lines
11 KiB
Vue

<template>
<view class="batch-container">
<common-header title="批量发货" theme="shipping" @back="goBack" />
<view class="batch-content">
<view class="batch-actions">
<uni-button type="primary" size="small" @click="selectAll">全选</uni-button>
<uni-button type="default" size="small" @click="clearSelection">清空</uni-button>
<uni-button type="primary" size="small" @click="batchShip" :disabled="selectedShippings.length === 0">
批量发货 ({{ selectedShippings.length }})
</uni-button>
<uni-button type="warn" size="small" @click="batchCancel" :disabled="selectedShippings.length === 0">
批量取消 ({{ selectedShippings.length }})
</uni-button>
</view>
<view class="filter-section">
<uni-search-bar
v-model="searchText"
placeholder="搜索发货记录"
@confirm="searchShippings"
@clear="clearSearch" />
</view>
<scroll-view
class="scroll-view"
scroll-y
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore">
<view v-if="!loading && shippingList.length === 0" class="empty-state">
<uni-icons type="car" size="80" color="#ddd" />
<text class="empty-text">暂无发货记录</text>
</view>
<view v-else class="shipping-list">
<view
v-for="(item, index) in shippingList"
:key="item.id || index"
class="shipping-item"
:class="{ 'selected': selectedShippings.includes(item.id) }"
@click="toggleSelection(item.id)">
<view class="item-checkbox">
<uni-icons
:type="selectedShippings.includes(item.id) ? 'checkbox-filled' : 'checkbox'"
size="20"
:color="selectedShippings.includes(item.id) ? '#7b68ee' : '#ccc'" />
</view>
<view class="item-content">
<view class="item-header">
<text class="order-no">{{ item.orderNo }}</text>
<text class="customer-name">{{ item.customerName }}</text>
</view>
<view class="item-info">
<text class="address">{{ item.address }}</text>
<text class="logistics">{{ getLogisticsText(item.logistics) }}</text>
</view>
<view class="item-footer">
<text class="ship-time">{{ formatTime(item.shipTime) }}</text>
<view class="status-badge" :class="{
'pending': item.status === 0,
'shipped': item.status === 1,
'delivered': item.status === 2,
'cancelled': item.status === 3
}">
{{ getStatusText(item.status) }}
</view>
</view>
</view>
</view>
</view>
<uni-load-more
:status="loadStatus"
:content-text="loadText"
@clickLoadMore="onLoadMore" />
</scroll-view>
</view>
</view>
</template>
<script>
import CommonHeader from '@/components/common-header/common-header.vue'
export default {
name: 'ShippingBatch',
components: {
CommonHeader
},
data() {
return {
shippingList: [],
selectedShippings: [],
loading: false,
refreshing: false,
searchText: '',
page: 1,
pageSize: 20,
hasMore: true,
loadStatus: 'more',
loadText: {
contentdown: '上拉加载更多',
contentrefresh: '正在加载...',
contentnomore: '没有更多了'
}
}
},
onLoad() {
this.loadShippingList()
},
methods: {
async loadShippingList(isRefresh = false) {
if (isRefresh) {
this.page = 1
this.hasMore = true
}
if (!this.hasMore && !isRefresh) return
this.loading = true
this.loadStatus = 'loading'
try {
const params = {
page: this.page,
pageSize: this.pageSize,
search: this.searchText
}
// TODO: Replace with actual API call
const response = await this.$http.get('/brewery/shipping', { params })
const { list, total } = response.data || {}
if (isRefresh) {
this.shippingList = list || []
this.selectedShippings = []
} else {
this.shippingList = [...this.shippingList, ...(list || [])]
}
this.hasMore = this.shippingList.length < total
this.loadStatus = this.hasMore ? 'more' : 'noMore'
} catch (error) {
console.error('加载发货列表失败:', error)
this.$modal.showToast('加载失败')
this.loadStatus = 'more'
} finally {
this.loading = false
this.refreshing = false
}
},
onRefresh() {
this.refreshing = true
this.loadShippingList(true)
},
onLoadMore() {
if (this.hasMore && !this.loading) {
this.page++
this.loadShippingList()
}
},
searchShippings() {
this.loadShippingList(true)
},
clearSearch() {
this.searchText = ''
this.loadShippingList(true)
},
toggleSelection(shippingId) {
const index = this.selectedShippings.indexOf(shippingId)
if (index > -1) {
this.selectedShippings.splice(index, 1)
} else {
this.selectedShippings.push(shippingId)
}
},
selectAll() {
// 只选择待发货的记录
this.selectedShippings = this.shippingList.filter(item => item.status === 0).map(item => item.id)
},
clearSelection() {
this.selectedShippings = []
},
batchShip() {
if (this.selectedShippings.length === 0) {
this.$modal.showToast('请先选择发货记录')
return
}
uni.showModal({
title: '确认发货',
content: `确定要批量发货选中的 ${this.selectedShippings.length} 个记录吗?`,
success: (res) => {
if (res.confirm) {
this.doBatchShip()
}
}
})
},
async doBatchShip() {
try {
// TODO: Replace with actual API call
const response = await this.$http.post('/brewery/shipping/batch-ship', {
shippingIds: this.selectedShippings
})
this.$modal.showToast('批量发货成功')
this.clearSelection()
this.loadShippingList(true)
} catch (error) {
console.error('批量发货失败:', error)
this.$modal.showToast('批量发货失败')
}
},
batchCancel() {
if (this.selectedShippings.length === 0) {
this.$modal.showToast('请先选择发货记录')
return
}
uni.showModal({
title: '确认取消',
content: `确定要批量取消选中的 ${this.selectedShippings.length} 个记录吗?`,
success: (res) => {
if (res.confirm) {
this.doBatchCancel()
}
}
})
},
async doBatchCancel() {
try {
// TODO: Replace with actual API call
const response = await this.$http.post('/brewery/shipping/batch-cancel', {
shippingIds: this.selectedShippings
})
this.$modal.showToast('批量取消成功')
this.clearSelection()
this.loadShippingList(true)
} catch (error) {
console.error('批量取消失败:', error)
this.$modal.showToast('批量取消失败')
}
},
getStatusText(status) {
const statusMap = {
0: '待发货',
1: '已发货',
2: '已送达',
3: '已取消'
}
return statusMap[status] || '未知'
},
getLogisticsText(logistics) {
const logisticsMap = {
sf: '顺丰快递',
ems: '中国邮政',
zt: '中通快递',
yd: '韵达快递',
sto: '申通快递',
other: '其他'
}
return logisticsMap[logistics] || '未知'
},
formatTime(time) {
if (!time) return ''
return time.substring(0, 16)
},
goBack() {
uni.navigateBack()
}
}
}
</script>
<style lang="scss" scoped>
.batch-container {
min-height: 100vh;
background: #f5f7fa;
}
.batch-content {
padding: 20rpx;
}
.batch-actions {
display: flex;
gap: 20rpx;
margin-bottom: 20rpx;
padding: 20rpx;
background: #fff;
border-radius: 16rpx;
}
.filter-section {
margin-bottom: 20rpx;
}
.scroll-view {
height: calc(100vh - 280rpx);
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
.empty-text {
font-size: 28rpx;
color: #999;
margin-top: 20rpx;
}
}
.shipping-list {
.shipping-item {
display: flex;
align-items: center;
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
&.selected {
background: #f8f6ff;
border: 2rpx solid #7b68ee;
}
.item-checkbox {
margin-right: 20rpx;
}
.item-content {
flex: 1;
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
.order-no {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.customer-name {
font-size: 26rpx;
color: #666;
}
}
.item-info {
margin-bottom: 12rpx;
.address {
display: block;
font-size: 24rpx;
color: #666;
margin-bottom: 4rpx;
}
.logistics {
font-size: 22rpx;
color: #999;
}
}
.item-footer {
display: flex;
justify-content: space-between;
align-items: center;
.ship-time {
font-size: 24rpx;
color: #999;
}
.status-badge {
padding: 6rpx 12rpx;
border-radius: 16rpx;
font-size: 22rpx;
&.pending {
background: #fff2e8;
color: #fa8c16;
}
&.shipped {
background: #e6f7ff;
color: #1890ff;
}
&.delivered {
background: #e8f5e8;
color: #52c41a;
}
&.cancelled {
background: #ffebee;
color: #f44336;
}
}
}
}
}
}
</style>