361 lines
8.0 KiB
Plaintext
361 lines
8.0 KiB
Plaintext
<template>
|
||
<view class="all-orders-container">
|
||
<!-- 头部标题栏 -->
|
||
<view class="page-header">
|
||
<view class="back-btn" @click="goBack">
|
||
<text class="back-icon">‹</text>
|
||
<text class="back-text">返回</text>
|
||
</view>
|
||
<text class="page-title">全部待接订单</text>
|
||
<view class="refresh-action" @click="loadOrders">
|
||
<text class="refresh-icon">🔄</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 订单列表区 -->
|
||
<scroll-view class="order-list-scroll" scroll-y="true" refresher-enabled="true" :refresher-triggered="isRefreshing" @refresherrefresh="onRefresh">
|
||
|
||
<view v-if="orders.length === 0" class="empty-state">
|
||
<image src="/static/images/no-order.png" class="empty-img" mode="aspectFit" />
|
||
<text class="empty-text">附近暂时没有待接订单</text>
|
||
</view>
|
||
|
||
<view v-for="order in orders" :key="order.id" class="order-card">
|
||
<view class="order-fee-center">
|
||
<text class="order-fee-text">¥{{ order.delivery_fee }}</text>
|
||
</view>
|
||
|
||
<view class="order-route-vertical">
|
||
<view class="route-point">
|
||
<text class="route-icon-pink">📍</text>
|
||
<text class="route-text-main">{{ order.pickup_address.detail || order.pickup_address.area }}</text>
|
||
</view>
|
||
<text class="route-arrow-down">↓</text>
|
||
<view class="route-point">
|
||
<text class="route-icon-home">🏠</text>
|
||
<text class="route-text-main">{{ order.delivery_address.detail || order.delivery_address.area }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="order-meta-info">
|
||
<text class="meta-label">距离: {{ order.distance }}km</text>
|
||
<text class="meta-label">预计: {{ order.estimated_time }}分钟</text>
|
||
<text class="meta-label">下单: {{ order.created_at }}</text>
|
||
</view>
|
||
|
||
<view class="order-actions-stack">
|
||
<button class="order-btn-full accept" @click="acceptOrder(order.id)">接受订单</button>
|
||
<button class="order-btn-full detail" @click="viewOrderDetail(order.id)">查看详情</button>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="orders.length > 0" class="list-bottom">
|
||
<text class="bottom-text">已加载全部订单</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="uts">
|
||
import supa, { supaReady } from '@/components/supadb/aksupainstance.uts'
|
||
import { getCurrentUserId } from '@/utils/store.uts'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
orders: [] as any[],
|
||
isRefreshing: false,
|
||
driverId: ''
|
||
}
|
||
},
|
||
async onLoad() {
|
||
await this.getDriverId()
|
||
await this.loadOrders()
|
||
},
|
||
methods: {
|
||
goBack() {
|
||
uni.navigateBack()
|
||
},
|
||
async getDriverId() {
|
||
try {
|
||
await supaReady
|
||
const userId = getCurrentUserId()
|
||
if (!userId) return
|
||
const res = await supa.from('ml_delivery_drivers').select('id').eq('user_id', userId).limit(1).execute()
|
||
if (res && Array.isArray(res.data) && res.data.length > 0) {
|
||
this.driverId = res.data[0].id
|
||
}
|
||
} catch (e) {
|
||
console.error('getDriverId error', e)
|
||
}
|
||
},
|
||
async loadOrders() {
|
||
this.isRefreshing = true
|
||
try {
|
||
await supaReady
|
||
const res = await supa.from('ml_delivery_tasks')
|
||
.select('*')
|
||
.is('driver_id', 'null')
|
||
.eq('status', 1)
|
||
.order('created_at', { ascending: false })
|
||
.execute()
|
||
|
||
if (res && Array.isArray(res.data)) {
|
||
this.orders = res.data.map((r: any) => this._transformTask(r))
|
||
}
|
||
} catch (e) {
|
||
console.error('loadAllOrders error', e)
|
||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||
} finally {
|
||
this.isRefreshing = false
|
||
}
|
||
},
|
||
onRefresh() {
|
||
this.loadOrders()
|
||
},
|
||
viewOrderDetail(orderId: string) {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/delivery/order-detail?id=${orderId}&status=1`
|
||
})
|
||
},
|
||
_transformTask(task: any) {
|
||
const parseAddress = (a: any) => {
|
||
if (!a) return { detail: '', area: '' }
|
||
let obj = a
|
||
if (typeof a === 'string') {
|
||
try { obj = JSON.parse(a) } catch (e) { obj = { detail: a } }
|
||
}
|
||
return {
|
||
detail: obj.detail || obj.address || '',
|
||
area: obj.area || obj.district || obj.city || '未知区域'
|
||
}
|
||
}
|
||
return {
|
||
id: task.id,
|
||
order_no: task.order_no || '无编号',
|
||
delivery_fee: Number(task.delivery_fee) || 0,
|
||
pickup_address: parseAddress(task.pickup_address),
|
||
delivery_address: parseAddress(task.delivery_address),
|
||
distance: Number(task.distance) || 0,
|
||
estimated_time: Number(task.estimated_time) || 0,
|
||
created_at: task.created_at
|
||
}
|
||
},
|
||
async acceptOrder(taskId: string) {
|
||
if (!this.driverId) {
|
||
uni.showToast({ title: '未找到配送员身份', icon: 'none' })
|
||
return
|
||
}
|
||
uni.showLoading({ title: '正抢单中...' })
|
||
try {
|
||
// 抢单逻辑:更新 driver_id 和状态
|
||
const res = await supa.from('ml_delivery_tasks')
|
||
.update({ driver_id: this.driverId, status: 2 })
|
||
.eq('id', taskId)
|
||
.is('driver_id', 'null') // 并发保护
|
||
.execute()
|
||
|
||
if (res && Array.isArray(res.data) && res.data.length > 0) {
|
||
// 同步订单状态
|
||
const orderId = (res.data[0] as any).order_id
|
||
if (orderId) {
|
||
await supa.from('ml_orders').update({ order_status: 2 }).eq('id', orderId).execute()
|
||
}
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '接单成功!', icon: 'success' })
|
||
setTimeout(() => {
|
||
uni.redirectTo({ url: '/pages/mall/delivery/index' })
|
||
}, 1500)
|
||
} else {
|
||
throw new Error('订单已被抢走')
|
||
}
|
||
} catch (e) {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '抢单失败,可能已被其他配送员接取', icon: 'none' })
|
||
this.loadOrders()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.all-orders-container {
|
||
background-color: #f7f8fa;
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.page-header {
|
||
background-color: #ffffff;
|
||
padding: 20rpx 30rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.back-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 10rpx 0;
|
||
}
|
||
|
||
.back-icon {
|
||
font-size: 40rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.back-text {
|
||
font-size: 28rpx;
|
||
margin-left: 5rpx;
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.refresh-action {
|
||
padding: 10rpx;
|
||
}
|
||
|
||
.order-list-scroll {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.order-card {
|
||
background-color: #ffffff;
|
||
margin: 20rpx;
|
||
padding: 30rpx;
|
||
border-radius: 20rpx;
|
||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
|
||
}
|
||
|
||
.order-fee-center {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20rpx 0;
|
||
margin-bottom: 30rpx;
|
||
border-bottom: 1rpx dashed #eee;
|
||
}
|
||
|
||
.order-fee-text {
|
||
font-size: 48rpx;
|
||
font-weight: bold;
|
||
color: #4CAF50;
|
||
text-align: center;
|
||
}
|
||
|
||
.order-route-vertical {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.route-point {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
}
|
||
|
||
.route-icon-pink {
|
||
font-size: 32rpx;
|
||
}
|
||
|
||
.route-icon-home {
|
||
font-size: 32rpx;
|
||
}
|
||
|
||
.route-text-main {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
text-align: center;
|
||
}
|
||
|
||
.route-arrow-down {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin: 10rpx 0;
|
||
}
|
||
|
||
.order-meta-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.meta-label {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.order-actions-stack {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.order-btn-full {
|
||
width: 100%;
|
||
height: 80rpx;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
border: none;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.order-btn-full.accept {
|
||
background-color: #4CAF50;
|
||
color: #fff;
|
||
}
|
||
|
||
.order-btn-full.detail {
|
||
background-color: #f0f0f0;
|
||
color: #333;
|
||
border: 1rpx solid #ddd;
|
||
}
|
||
|
||
.empty-state {
|
||
padding-top: 200rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.empty-img {
|
||
width: 240rpx;
|
||
height: 240rpx;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.list-bottom {
|
||
padding: 40rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.bottom-text {
|
||
font-size: 24rpx;
|
||
color: #ccc;
|
||
}
|
||
</style>
|