403 lines
9.6 KiB
Plaintext
403 lines
9.6 KiB
Plaintext
<template>
|
||
<view class="container">
|
||
<view class="header">
|
||
<text class="back-link" @click="goBack">⬅ 返回</text>
|
||
<text class="title">商家发货管理</text>
|
||
</view>
|
||
|
||
<!-- 筛选栏 -->
|
||
<view class="filter-bar">
|
||
<view class="filter-item" :class="{active: currentFilter === 'all'}" @click="setFilter('all')">全部</view>
|
||
<view class="filter-item" :class="{active: currentFilter === 'pending'}" @click="setFilter('pending')">待发货</view>
|
||
<view class="filter-item" :class="{active: currentFilter === 'shipped'}" @click="setFilter('shipped')">已发货</view>
|
||
</view>
|
||
|
||
<scroll-view class="order-list" scroll-y="true">
|
||
<view v-for="(order, index) in filteredOrders" :key="order.order_no" class="order-card">
|
||
<view class="order-header">
|
||
<text class="order-no">订单号: {{ order.order_no }}</text>
|
||
<text class="order-status">{{ getStatusText(order.status) }}</text>
|
||
</view>
|
||
|
||
<view class="order-body">
|
||
<view class="info-row">
|
||
<text class="label">下单时间:</text>
|
||
<text class="value">{{ order.created_at }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="label">收件人:</text>
|
||
<text class="value">{{ order.receiver_name }} {{ order.receiver_masked_phone }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="label">运单号:</text>
|
||
<text class="value">{{ order.tracking_no ? order.tracking_no + ' (' + order.carrier + ')' : '暂无运单号' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="order-footer">
|
||
<button v-if="order.status === 'PENDING' || order.status === 'ORDER_PLACED'" class="btn-primary" @click="openShipModal(order)">去发货</button>
|
||
<button class="btn-secondary" @click="viewDetail(order)">详情</button>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="filteredOrders.length === 0" class="empty-state">
|
||
<text>暂无订单</text>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 发货弹窗 -->
|
||
<view v-if="showShipModal" class="modal-mask">
|
||
<view class="modal-content">
|
||
<view class="modal-header">
|
||
<view class="title-box">
|
||
<text class="modal-title">订单发货</text>
|
||
<text v-if="selectedOrder != null" class="modal-order-no">单号: {{ selectedOrder!.order_no }}</text>
|
||
</view>
|
||
<text class="close-btn" @click="showShipModal = false">×</text>
|
||
</view>
|
||
<view class="modal-body">
|
||
<view class="form-item">
|
||
<text class="label">选择承运商</text>
|
||
<picker :range="carriers" range-key="label" @change="onCarrierChange">
|
||
<view class="picker-val">{{ currentCarrier || '请选择' }}</view>
|
||
</picker>
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="label">运单号</text>
|
||
<view class="input-wrapper">
|
||
<input v-model="trackingNo" placeholder="请输入或扫码录入" class="input" />
|
||
<view class="scan-btn" @click="scanCode">
|
||
<text class="scan-icon">📷</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="modal-footer">
|
||
<button class="btn-cancel" @click="showShipModal = false">取消</button>
|
||
<button class="btn-confirm" @click="confirmShip">确认发货</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script uts>
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
import { mockService, MockOrder, LogisticsConstants } from './mock-service.uts'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
currentFilter: 'all',
|
||
orders: [] as MockOrder[],
|
||
showShipModal: false,
|
||
selectedOrder: null as MockOrder | null,
|
||
carriers: LogisticsConstants.CARRIERS,
|
||
currentCarrier: '顺丰速运',
|
||
trackingNo: ''
|
||
}
|
||
},
|
||
onShow() {
|
||
this.loadData()
|
||
},
|
||
computed: {
|
||
filteredOrders() : MockOrder[] {
|
||
const list = this.orders
|
||
if (this.currentFilter === 'all') return list
|
||
|
||
// 待发货:包含已下单和待发货状态
|
||
if (this.currentFilter === 'pending') {
|
||
return list.filter((o : MockOrder) : boolean =>
|
||
o.status === 'PENDING' || o.status === 'ORDER_PLACED'
|
||
)
|
||
}
|
||
|
||
// 已发货:只要已经有了运单号或状态已转为发货后的
|
||
if (this.currentFilter === 'shipped') {
|
||
const shippedStates = ['SHIPPED', 'IN_TRANSIT', 'OUT_FOR_DELIVERY', 'READY_FOR_PICKUP', 'DELIVERED', 'EXCEPTION', 'RETURNED']
|
||
return list.filter((o : MockOrder) : boolean => shippedStates.includes(o.status))
|
||
}
|
||
return []
|
||
}
|
||
},
|
||
methods: {
|
||
goBack() {
|
||
uni.navigateBack()
|
||
},
|
||
async loadData() {
|
||
const data = await mockService.getMockOrders()
|
||
this.orders = [...data]
|
||
},
|
||
setFilter(filter: string) {
|
||
this.currentFilter = filter
|
||
},
|
||
getStatusText(status: string) : string {
|
||
return mockService.getStatusText(status)
|
||
},
|
||
openShipModal(order: MockOrder) {
|
||
this.selectedOrder = order
|
||
this.currentCarrier = '顺丰速运'
|
||
this.trackingNo = ''
|
||
this.showShipModal = true
|
||
},
|
||
onCarrierChange(e: any) {
|
||
const index = e.detail.value as number
|
||
this.currentCarrier = this.carriers[index]['value'] as string
|
||
},
|
||
async confirmShip() {
|
||
if (!this.trackingNo) {
|
||
uni.showToast({ title: '请输入运单号', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
uni.showLoading({ title: '提交中...' })
|
||
|
||
const orderNo = this.selectedOrder?.order_no
|
||
if (orderNo != null) {
|
||
// 1. 同步修改 数据库 中的数据
|
||
const success = await mockService.bindShipment(orderNo, this.currentCarrier, this.trackingNo)
|
||
|
||
if (success) {
|
||
// 2. 直接修改本地选中的订单对象状态,确保 Vue 响应式立即触发
|
||
if (this.selectedOrder != null) {
|
||
const target = this.selectedOrder!
|
||
target.status = 'SHIPPED'
|
||
target.carrier = this.currentCarrier
|
||
target.tracking_no = this.trackingNo
|
||
}
|
||
|
||
// 3. 重新加载数据(从数据库刷新)
|
||
await this.loadData()
|
||
|
||
uni.hideLoading()
|
||
this.showShipModal = false
|
||
uni.showToast({ title: '发货成功' })
|
||
} else {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '发货失败,请重试', icon: 'none' })
|
||
}
|
||
}
|
||
},
|
||
viewDetail(order: MockOrder) {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/delivery/test/merchant-order-detail?order_no=${order.order_no}`
|
||
})
|
||
},
|
||
scanCode() {
|
||
uni.scanCode({
|
||
success: (res) => {
|
||
this.trackingNo = res.result
|
||
},
|
||
fail: (_) => {
|
||
uni.showToast({
|
||
title: '该功能尚未完成',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.container {
|
||
padding: 20px;
|
||
background-color: #f5f5f5;
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.order-list {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
.header {
|
||
padding: 30rpx 0;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
}
|
||
.back-link {
|
||
font-size: 26rpx;
|
||
color: #007AFF;
|
||
cursor: pointer;
|
||
}
|
||
.title {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
.filter-bar {
|
||
display: flex;
|
||
background-color: #fff;
|
||
border-radius: 12rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
.filter-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
padding: 20rpx 0;
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
}
|
||
.filter-item.active {
|
||
color: #007AFF;
|
||
font-weight: bold;
|
||
border-bottom: 4rpx solid #007AFF;
|
||
}
|
||
.order-card {
|
||
background-color: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 24rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
.order-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
border-bottom: 1rpx solid #eee;
|
||
padding-bottom: 16rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
.order-no {
|
||
font-size: 26rpx;
|
||
color: #999;
|
||
}
|
||
.order-status {
|
||
font-size: 26rpx;
|
||
color: #f39c12;
|
||
}
|
||
.info-row {
|
||
display: flex;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
.label {
|
||
width: 140rpx;
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
.value {
|
||
flex: 1;
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
}
|
||
.order-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 20rpx;
|
||
gap: 20rpx;
|
||
}
|
||
.btn-primary {
|
||
background-color: #007AFF;
|
||
color: #fff;
|
||
font-size: 24rpx;
|
||
padding: 10rpx 30rpx;
|
||
border-radius: 30rpx;
|
||
}
|
||
.btn-secondary {
|
||
background-color: #fff;
|
||
color: #666;
|
||
border: 1rpx solid #ddd;
|
||
font-size: 24rpx;
|
||
padding: 10rpx 30rpx;
|
||
border-radius: 30rpx;
|
||
}
|
||
.modal-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0,0,0,0.5);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 100;
|
||
}
|
||
.modal-content {
|
||
width: 80%;
|
||
background-color: #fff;
|
||
border-radius: 20rpx;
|
||
padding: 40rpx;
|
||
}
|
||
.modal-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
.title-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.modal-title {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
.modal-order-no {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 8rpx;
|
||
}
|
||
.close-btn {
|
||
font-size: 40rpx;
|
||
color: #ccc;
|
||
padding: 10rpx;
|
||
}
|
||
.form-item {
|
||
margin-bottom: 30rpx;
|
||
}
|
||
.picker-val {
|
||
border: 1rpx solid #ddd;
|
||
padding: 16rpx;
|
||
border-radius: 8rpx;
|
||
margin-top: 10rpx;
|
||
}
|
||
.input-wrapper {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-top: 10rpx;
|
||
}
|
||
.input {
|
||
flex: 1;
|
||
border: 1rpx solid #ddd;
|
||
padding: 16rpx;
|
||
padding-right: 80rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
.scan-btn {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 80rpx;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 10;
|
||
}
|
||
.scan-icon {
|
||
font-size: 36rpx;
|
||
}
|
||
.modal-footer {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-top: 40rpx;
|
||
}
|
||
.btn-confirm {
|
||
flex: 1;
|
||
background-color: #007AFF;
|
||
color: #fff;
|
||
}
|
||
.btn-cancel {
|
||
flex: 1;
|
||
background-color: #eee;
|
||
color: #666;
|
||
}
|
||
</style>
|