数据库分析和对应不同角色页面

This commit is contained in:
not-like-juvenile
2026-02-06 15:10:18 +08:00
parent 33030bd20b
commit 56ae71babf
30 changed files with 3510 additions and 21 deletions

View File

@@ -0,0 +1,327 @@
<template>
<view class="container">
<view class="header">
<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" @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: ['YUNDA', 'YTO', 'ZTO', 'STO', 'SF'],
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: {
loadData() {
// 使用展开运算符创建新数组引用,确保 Vue 响应式触发
this.orders = [...mockService.getMockOrders()]
},
setFilter(filter: string) {
this.currentFilter = filter
},
getStatusText(status: string) : string {
const maps = {
'PENDING': '待发货',
'SHIPPED': '已发货',
'DELIVERED': '已签收',
'OUT_FOR_DELIVERY': '派送中',
'EXCEPTION': '包裹异常'
}
return (maps[status] != null) ? maps[status] : status
},
openShipModal(order: MockOrder) {
this.selectedOrder = order
this.currentCarrier = 'YUNDA'
this.trackingNo = ''
this.showShipModal = true
},
onCarrierChange(e: any) {
const index = e.detail.value as number
this.currentCarrier = this.carriers[index]
},
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 scoped>
.container {
padding: 20px;
background-color: #f5f5f5;
height: 100vh;
display: flex;
flex-direction: column;
}
.order-list {
flex: 1;
overflow: hidden;
}
.header {
padding: 30rpx 0;
}
.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>