Files
medical-mall/pages/mall/delivery/test/merchant-order-list.uvue
2026-02-09 08:54:26 +08:00

348 lines
8.6 KiB
Plaintext
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="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 v-if="order.tracking_no" class="info-row">
<text class="label">运单号:</text>
<text class="value">{{ order.tracking_no }} ({{ order.carrier }})</text>
</view>
</view>
<view class="order-footer">
<button v-if="order.status === 'PENDING'" 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">
<text class="modal-title">订单发货</text>
<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>
<input v-model="trackingNo" placeholder="请输入或扫码录入" class="input" />
</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 } from './mock-service.uts'
export default {
data() {
return {
currentFilter: 'all',
orders: [] as MockOrder[],
showShipModal: false,
selectedOrder: null as MockOrder | null,
carriers: [
{ label: '韵达快递', value: '韵达快递' },
{ label: '圆通速递', value: '圆通速递' },
{ label: '中通快递', value: '中通快递' },
{ label: '申通快递', value: '申通快递' },
{ label: '顺丰速运', value: '顺丰速运' }
],
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')
}
if (this.currentFilter === 'shipped') {
// 除了待发货,其余所有状态(运输中、派送中、已签收、异常)都属于“已发货”范畴
return list.filter((o : MockOrder) : boolean => o.status !== 'PENDING')
}
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 {
const maps = {
'PENDING': '待发货',
'SHIPPED': '已发货',
'IN_TRANSIT': '运输中',
'DELIVERED': '已签收',
'OUT_FOR_DELIVERY': '派送中',
'EXCEPTION': '包裹异常'
}
return (maps[status] != null) ? maps[status] : 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
},
async confirmShip() {
if (!this.trackingNo) {
uni.showToast({ title: '请输入运单号', icon: 'none' })
return
}
uni.showLoading({ title: '提交中...' })
// 调用 Mock 入库接口,实现同步修改
setTimeout(() => {
uni.hideLoading()
const orderNo = this.selectedOrder?.order_no
if (orderNo != null) {
// 1. 同步修改 Mock Service 中的原始数据
mockService.bindShipment(orderNo, this.currentCarrier, this.trackingNo)
// 2. 直接修改本地选中的订单对象状态,确保 Vue 响应式立即触发
if (this.selectedOrder != null) {
const target = this.selectedOrder!
target.status = 'SHIPPED'
target.carrier = this.currentCarrier
target.tracking_no = this.trackingNo
}
// 3. 重新加载数据(通过新引用触发 computed
this.loadData()
}
this.showShipModal = false
uni.showToast({ title: '发货成功' })
}, 1000)
},
viewDetail(order: MockOrder) {
uni.navigateTo({
url: `/pages/mall/delivery/test/merchant-order-detail?order_no=${order.order_no}`
})
}
}
}
</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;
justify-content: space-between;
margin-bottom: 30rpx;
}
.modal-title {
font-size: 32rpx;
font-weight: bold;
}
.form-item {
margin-bottom: 30rpx;
}
.picker-val {
border: 1rpx solid #ddd;
padding: 16rpx;
border-radius: 8rpx;
margin-top: 10rpx;
}
.input {
border: 1rpx solid #ddd;
padding: 16rpx;
border-radius: 8rpx;
margin-top: 10rpx;
}
.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>