Files
medical-mall/pages/mall/delivery/index.uvue
2026-02-02 18:20:22 +08:00

1221 lines
32 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.
<!-- 配送端首页 - UTS Android 兼容 -->
<template>
<view class="delivery-container">
<!-- 头部状态栏 -->
<view class="header">
<view class="driver-info">
<image :src="driverInfo.avatar_url || '/static/default-avatar.png'" class="avatar" mode="aspectFit" />
<view class="driver-details">
<text class="driver-name">{{ driverInfo.real_name }}</text>
<text class="work-status" :class="getWorkStatusClass()">{{ getWorkStatusText() }}</text>
</view>
</view>
<view class="status-switch">
<switch :checked="isOnline" @change="toggleWorkStatus" color="#4CAF50" />
<text class="switch-label">{{ isOnline ? '在线接单' : '离线休息' }}</text>
</view>
</view>
<!-- 今日统计 -->
<view class="stats-section">
<text class="section-title">今日数据</text>
<view class="stats-grid">
<view class="stat-item">
<text class="stat-value">{{ todayStats.completed_orders }}</text>
<text class="stat-label">完成订单</text>
</view>
<view class="stat-item">
<text class="stat-value">¥{{ todayStats.total_earning }}</text>
<text class="stat-label">总收入</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ todayStats.total_distance }}km</text>
<text class="stat-label">配送距离</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ todayStats.avg_rating }}</text>
<text class="stat-label">平均评分</text>
</view>
</view>
</view>
<!-- 当前配送任务 -->
<view v-if="currentTask" class="current-task-section">
<text class="section-title">当前任务</text>
<view class="task-card">
<view class="task-header">
<text class="task-id">订单号: {{ currentTask.order_no }}</text>
<text class="task-status" :class="getTaskStatusClass(currentTask.status)">{{ getTaskStatusText(currentTask.status) }}</text>
</view>
<view class="task-addresses">
<view class="address-item">
<text class="address-icon">📍</text>
<view class="address-info">
<text class="address-label">取货地址</text>
<text class="address-text">{{ currentTask.pickup_address.detail }}</text>
<text class="contact-info">联系人: {{ currentTask.pickup_contact.name }} {{ currentTask.pickup_contact.phone }}</text>
</view>
</view>
<view class="address-line"></view>
<view class="address-item">
<text class="address-icon">🏠</text>
<view class="address-info">
<text class="address-label">收货地址</text>
<text class="address-text">{{ currentTask.delivery_address.detail }}</text>
<text class="contact-info">联系人: {{ currentTask.delivery_contact.name }} {{ currentTask.delivery_contact.phone }}</text>
</view>
</view>
</view>
<view class="task-details">
<text class="task-info">配送费: ¥{{ currentTask.delivery_fee }}</text>
<text class="task-info">预计距离: {{ currentTask.distance }}km</text>
<text class="task-info">预计时间: {{ currentTask.estimated_time }}分钟</text>
</view>
<view class="task-actions">
<!-- 根据状态显示不同的操作按钮 -->
<button v-if="currentTask.status === 1" class="action-btn primary" @click="acceptTask">接受任务</button>
<button v-if="currentTask.status === 2" class="action-btn primary" @click="startPickup">开始取货</button>
<button v-if="currentTask.status === 3" class="action-btn primary" @click="confirmPickup">确认取货</button>
<button v-if="currentTask.status === 4" class="action-btn primary" @click="startDelivery">开始配送</button>
<button v-if="currentTask.status === 5" class="action-btn primary" @click="showConfirmDeliveryDialog">确认送达</button>
<button class="action-btn secondary" @click="contactCustomer">联系客户</button>
<button class="action-btn secondary" @click="viewNavigation">查看导航</button>
<button class="action-btn secondary" @click="viewOrderDetail">查看详情</button>
</view>
</view>
</view>
<!-- 可接取订单 -->
<view v-if="!currentTask && isOnline" class="available-orders-section">
<view class="section-header">
<text class="section-title">附近订单</text>
<view class="section-header-actions">
<text class="refresh-btn" @click="refreshOrders">🔄 刷新</text>
<!-- 当可接取订单达到上限时显示更多入口 -->
<text v-if="availableOrders && availableOrders.length >= 20" class="more-btn" @click="goToAllOrders">更多 ➜</text>
</view>
</view>
<view v-if="availableOrders.length === 0" class="empty-orders">
<text class="empty-text">暂无可接取订单</text>
<text class="empty-subtitle">请保持在线状态,有新订单会自动推送</text>
</view>
<view v-for="order in availableOrders" :key="order.id" class="order-card">
<view class="order-header">
<text class="order-id">{{ order.order_no }}</text>
<text class="order-fee">¥{{ order.delivery_fee }}</text>
</view>
<view class="order-route">
<view class="route-item">
<text class="route-icon">📍</text>
<text class="route-text">{{ order.pickup_address.area }}</text>
</view>
<text class="route-arrow">→</text>
<view class="route-item">
<text class="route-icon">🏠</text>
<text class="route-text">{{ order.delivery_address.area }}</text>
</view>
</view>
<view class="order-info">
<text class="info-item">距离: {{ order.distance }}km</text>
<text class="info-item">预计: {{ order.estimated_time }}分钟</text>
<text class="info-item">下单: {{ formatTime(order.created_at) }}</text>
</view>
<view class="order-actions">
<button class="order-btn accept" @click="acceptOrder(order.id)">接受订单</button>
<button class="order-btn detail" @click="viewOrderDetail(order.id)">查看详情</button>
</view>
</view>
</view>
<!-- 历史记录快捷入口 -->
<view class="quick-actions-section">
<text class="section-title">快捷功能</text>
<view class="actions-grid">
<view class="action-item" @click="goToOrderHistory">
<text class="action-icon">📋</text>
<text class="action-text">历史订单</text>
</view>
<view class="action-item" @click="goToEarnings">
<text class="action-icon">💰</text>
<text class="action-text">收入明细</text>
</view>
<view class="action-item" @click="goToProfile">
<text class="action-icon">👤</text>
<text class="action-text">个人资料</text>
</view>
<view class="action-item" @click="goToSettings">
<text class="action-icon">⚙️</text>
<text class="action-text">设置</text>
</view>
</view>
</view>
</view>
</template>
<script lang="uts">
import type {
DeliveryDriverType,
DeliveryTaskType
} from '@/types/mall-types.uts'
import supa, { supaReady } from '@/components/supadb/aksupainstance.uts'
import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
type TodayStatsType = {
completed_orders: number
total_earning: string
total_distance: number
avg_rating: number
}
type AddressInfoType = {
detail: string
area: string
}
type ContactInfoType = {
name: string
phone: string
}
type CurrentTaskType = {
id: string
order_no: string
status: number
pickup_address: AddressInfoType
delivery_address: AddressInfoType
pickup_contact: ContactInfoType
delivery_contact: ContactInfoType
delivery_fee: number
distance: number
estimated_time: number
created_at: string
}
type AvailableOrderType = {
id: string
order_no: string
pickup_address: AddressInfoType
delivery_address: AddressInfoType
delivery_fee: number
distance: number
estimated_time: number
created_at: string
}
export default {
data() {
return {
isOnline: true,
// 防抖:记录上次刷新时间,避免 onShow 导致的频繁 refresh
lastRefreshAt: 0,
// 控制是否启用自动刷新onShow——默认关闭避免频繁或意外刷新
enableAutoRefresh: false,
driverInfo: {
id: '',
user_id: '',
real_name: '配送员',
id_card: '',
driver_license: '',
vehicle_type: 1,
vehicle_number: '',
work_status: 1,
current_location: null,
service_areas: [],
rating: 5.0,
total_orders: 0,
auth_status: 1,
created_at: '',
updated_at: ''
} as DeliveryDriverType,
todayStats: {
completed_orders: 0,
total_earning: '0.00',
total_distance: 0,
avg_rating: 5.0
} as TodayStatsType,
currentTask: null as CurrentTaskType | null,
availableOrders: [] as Array<AvailableOrderType>
}
},
async onLoad() {
// 确保 userProfile 已加载,以便 getCurrentUserId 能返回正确值
try {
await getCurrentUser()
} catch (e) {
console.warn('getCurrentUser failed on onLoad', e)
}
await this.loadDriverInfo()
await this.loadTodayStats()
await this.loadCurrentTask()
await this.loadAvailableOrders()
},
async onShow() {
// 自动刷新已被禁用enableAutoRefresh = false以避免页面抖动。
// 如需临时启用,可在控制台设置 `this.enableAutoRefresh = true`。
if (!this.enableAutoRefresh) {
console.log('onShow: auto refresh disabled')
return
}
const now = Date.now()
if (this.lastRefreshAt && (now - this.lastRefreshAt < 5000)) {
console.log('onShow: skipped refresh (debounced)')
return
}
this.lastRefreshAt = now
await this.refreshData()
},
methods: {
// 加载配送员信息
async loadDriverInfo() {
try {
const ready = await Promise.race([supaReady, new Promise(resolve => setTimeout(() => resolve(false), 1500))])
if (!ready) console.warn('supaReady timeout/failed in loadDriverInfo - proceeding')
const userId = getCurrentUserId()
if (!userId) return
// 先按 user_id 查询userId 可能是 ak_users.id 或 auth.users.id
let res = await supa.from('ml_delivery_drivers').select('*').eq('user_id', userId).limit(1).execute()
console.log('loadDriverInfo: try user_id=', userId, 'res=', res)
if (!(res && (res.data instanceof Array) && res.data.length > 0)) {
// 回退:尝试从 ak_users 表根据 auth_id 查出 ak_users.id
const akRes = await supa.from('ak_users').select('id').eq('auth_id', userId).limit(1).execute()
console.log('loadDriverInfo: ak_users lookup by auth_id=', userId, 'akRes=', akRes)
let akId = ''
if (akRes && Array.isArray(akRes.data) && akRes.data.length > 0) {
akId = (akRes.data[0] as any).id
}
if (akId) {
res = await supa.from('ml_delivery_drivers').select('*').eq('user_id', akId).limit(1).execute()
console.log('loadDriverInfo: retry user_id with akId=', akId, 'res=', res)
}
}
if (res && (res.data instanceof Array) && res.data.length > 0) {
this.driverInfo = Object.assign(this.driverInfo, res.data[0])
}
} catch (e) {
console.error('loadDriverInfo error', e)
}
},
async loadTodayStats() {
try {
const ready = await Promise.race([supaReady, new Promise(resolve => setTimeout(() => resolve(false), 1500))])
if (!ready) console.warn('supaReady timeout/failed in loadTodayStats - proceeding')
const driverId = this.driverInfo.id || null
if (!driverId) return
const start = new Date()
start.setHours(0,0,0,0)
const end = new Date()
end.setHours(23,59,59,999)
const res = await supa.from('ml_delivery_tasks')
.select('id,delivery_fee,distance,created_at,status')
.eq('driver_id', driverId)
.gte('created_at', start.toISOString())
.lte('created_at', end.toISOString())
.execute()
if (res && res.data) {
const rows = res.data as Array<any>
const completed = rows.filter(r => r.status >= 5).length
const earning = rows.reduce((s, r) => s + (Number(r.delivery_fee) || 0), 0)
const distance = rows.reduce((s, r) => s + (Number(r.distance) || 0), 0)
this.todayStats = {
completed_orders: completed,
total_earning: earning.toFixed(2),
total_distance: Number(distance.toFixed(2)),
avg_rating: this.driverInfo.rating || 0
}
}
} catch (e) {
console.error('loadTodayStats error', e)
}
},
async loadCurrentTask() {
try {
const ready = await Promise.race([supaReady, new Promise(resolve => setTimeout(() => resolve(false), 1500))])
if (!ready) console.warn('supaReady timeout/failed in loadCurrentTask - proceeding')
const driverId = this.driverInfo.id || null
if (!driverId) {
this.currentTask = null
return
}
const res = await supa.from('ml_delivery_tasks')
.select('*')
.eq('driver_id', driverId)
.lt('status', 5)
.order('created_at', { ascending: false })
.limit(1)
.execute()
console.log('loadCurrentTask: driverId=', driverId, 'res=', res)
if (res && Array.isArray(res.data) && res.data.length > 0) {
this.currentTask = this._transformTask(res.data[0])
} else {
this.currentTask = null
}
} catch (e) {
console.error('loadCurrentTask error', e)
}
},
async loadAvailableOrders() {
// 如果当前不在线或已有任务,直接清空并返回
if (!this.isOnline || this.currentTask) {
this.availableOrders = []
return
}
// 在加载过程中先清空,避免显示过期或闪现的数据
this.availableOrders = []
try {
const ready = await Promise.race([supaReady, new Promise(resolve => setTimeout(() => resolve(false), 1500))])
if (!ready) console.warn('supaReady timeout/failed in loadAvailableOrders - proceeding')
console.log('loadAvailableOrders: supa session=', supa.getSession && supa.getSession())
console.log('loadAvailableOrders: getCurrentUserId=', getCurrentUserId())
const res = await supa.from('ml_delivery_tasks')
.select('*')
.is('driver_id', 'null')
.eq('status', 1)
.range(0, 19)
.execute()
console.log('loadAvailableOrders: query result=', res)
if (res && Array.isArray(res.data)) {
const fetched = (res.data as Array<any>).map((r:any) => this._transformTask(r))
// 再次检查 currentTask避免并发情况下短暂展示可接单
if (this.currentTask) {
this.availableOrders = []
} else {
this.availableOrders = fetched
}
}
} catch (e) {
console.error('loadAvailableOrders error', e)
this.availableOrders = []
}
},
// 将 DB 行转换为页面期望的结构
_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 } }
}
const detail = obj.detail || obj.address || obj.full_address || obj.address_detail || obj.name || ''
const area = (obj.city || obj.district || obj.area || '')
return { detail, area }
}
const parseContact = (c: any) => {
if (!c) return { name: '', phone: '' }
let obj = c
if (typeof c === 'string') {
try { obj = JSON.parse(c) } catch (e) { obj = { name: c } }
}
return { name: obj.name || obj.contact_name || obj.receiver_name || '', phone: obj.phone || obj.mobile || obj.contact_phone || '' }
}
return {
id: task.id,
order_no: task.order_no || task.orderNo || task.trade_no || '',
status: Number(task.status) || 1,
pickup_address: parseAddress(task.pickup_address),
delivery_address: parseAddress(task.delivery_address),
pickup_contact: parseContact(task.pickup_contact),
delivery_contact: parseContact(task.delivery_contact),
delivery_fee: Number(task.delivery_fee) || 0,
distance: Number(task.distance) || 0,
estimated_time: Number(task.estimated_time) || 0,
created_at: task.created_at || task.createdAt || ''
}
},
// 刷新数据
async refreshData() {
await this.loadTodayStats()
await this.loadCurrentTask()
await this.loadAvailableOrders()
},
// 刷新订单列表
refreshOrders() {
this.loadAvailableOrders()
uni.showToast({
title: '刷新成功',
icon: 'success'
})
},
// 切换工作状态
toggleWorkStatus(event: UniSwitchChangeEvent) {
this.isOnline = event.detail.value
if (this.isOnline) {
this.startWork()
} else {
this.stopWork()
}
},
// 开始工作
startWork() {
// TODO: 调用API开始工作上传位置
this.loadAvailableOrders()
uni.showToast({
title: '已上线接单',
icon: 'success'
})
},
// 停止工作
stopWork() {
// TODO: 调用API停止工作
this.availableOrders = []
uni.showToast({
title: '已下线休息',
icon: 'none'
})
},
// 获取工作状态样式
getWorkStatusClass(): string {
return this.isOnline ? 'status-online' : 'status-offline'
},
// 获取工作状态文本
getWorkStatusText(): string {
return this.isOnline ? '在线中' : '已离线'
},
// 获取任务状态样式
getTaskStatusClass(status: number): string {
switch (status) {
case 1: return 'task-pending'
case 2: return 'task-accepted'
case 3: return 'task-picking'
case 4: return 'task-picked'
case 5: return 'task-delivering'
default: return 'task-default'
}
},
// 获取任务状态文本
getTaskStatusText(status: number): string {
switch (status) {
case 1: return '待接取'
case 2: return '已接取'
case 3: return '取货中'
case 4: return '已取货'
case 5: return '配送中'
default: return '未知状态'
}
},
// 格式化时间
formatTime(timeStr: string): string {
const date = new Date(timeStr)
const now = new Date()
const diff = now.getTime() - date.getTime()
const minutes = Math.floor(diff / (1000 * 60))
if (minutes < 60) {
return `${minutes}分钟前`
} else {
return `${Math.floor(minutes / 60)}小时前`
}
},
// 任务操作方法
async acceptTask() {
if (!this.currentTask) return
try {
const ready = await Promise.race([supaReady, new Promise(resolve => setTimeout(() => resolve(false), 1500))])
if (!ready) console.warn('supaReady timeout/failed in acceptTask - proceeding')
const driverId = this.driverInfo.id || null
if (!driverId) throw new Error('无配送员ID')
const res = await supa.from('ml_delivery_tasks').update({ driver_id: driverId, status: 2 }).eq('id', this.currentTask.id).execute()
if (res && !res.error) {
this.currentTask.status = 2
uni.showToast({ title: '任务已接受', icon: 'success' })
}
} catch (e) {
console.error('acceptTask error', e)
uni.showToast({ title: '接受任务失败', icon: 'none' })
}
},
async startPickup() {
if (!this.currentTask) return
try {
const ready = await Promise.race([supaReady, new Promise(resolve => setTimeout(() => resolve(false), 1500))])
if (!ready) console.warn('supaReady timeout/failed in startPickup - proceeding')
const res = await supa.from('ml_delivery_tasks').update({ status: 3 }).eq('id', this.currentTask.id).execute()
if (res && !res.error) {
this.currentTask.status = 3
uni.showToast({ title: '开始取货', icon: 'success' })
}
} catch (e) {
console.error('startPickup error', e)
uni.showToast({ title: '操作失败', icon: 'none' })
}
},
async confirmPickup() {
if (!this.currentTask) return
try {
const ready = await Promise.race([supaReady, new Promise(resolve => setTimeout(() => resolve(false), 1500))])
if (!ready) console.warn('supaReady timeout/failed in confirmPickup - proceeding')
const res = await supa.from('ml_delivery_tasks').update({ status: 4, pickup_time: new Date().toISOString() }).eq('id', this.currentTask.id).execute()
if (res && !res.error) {
this.currentTask.status = 4
uni.showToast({ title: '取货完成', icon: 'success' })
}
} catch (e) {
console.error('confirmPickup error', e)
uni.showToast({ title: '操作失败', icon: 'none' })
}
},
async startDelivery() {
if (!this.currentTask) return
try {
const ready = await Promise.race([supaReady, new Promise(resolve => setTimeout(() => resolve(false), 1500))])
if (!ready) console.warn('supaReady timeout/failed in startDelivery - proceeding')
const res = await supa.from('ml_delivery_tasks').update({ status: 5 }).eq('id', this.currentTask.id).execute()
if (res && !res.error) {
this.currentTask.status = 5
uni.showToast({ title: '开始配送', icon: 'success' })
}
} catch (e) {
console.error('startDelivery error', e)
uni.showToast({ title: '操作失败', icon: 'none' })
}
},
// 显示确认送达弹框
showConfirmDeliveryDialog() {
uni.showModal({
title: '确认送达',
content: '确认商品已送到顾客手中?',
success: (res) => {
if (res.confirm) {
this.confirmDelivery()
}
}
})
},
async confirmDelivery() {
if (!this.currentTask) return
try {
const ready = await Promise.race([supaReady, new Promise(resolve => setTimeout(() => resolve(false), 1500))])
if (!ready) console.warn('supaReady timeout/failed in confirmDelivery - proceeding')
const res = await supa.from('ml_delivery_tasks').update({ status: 6, delivered_time: new Date().toISOString() }).eq('id', this.currentTask.id).execute()
if (res && !res.error) {
const completedOrder = { ...this.currentTask }
uni.setStorageSync('completed_order_for_history', completedOrder)
uni.showToast({ title: '配送完成', icon: 'success' })
this.currentTask = null
this.loadAvailableOrders()
}
} catch (e) {
console.error('confirmDelivery error', e)
uni.showToast({ title: '操作失败', icon: 'none' })
}
},
contactCustomer() {
if (this.currentTask) {
uni.makePhoneCall({
phoneNumber: this.currentTask.delivery_contact.phone
})
}
},
viewNavigation() {
// TODO: 打开地图导航
uni.showToast({
title: '打开导航',
icon: 'none'
})
},
// 查看订单详情(跳转到 order-detail 页面)
viewOrderDetail(orderId?: string,status?:number) {
if (orderId && status) {
uni.navigateTo({
url: `/pages/mall/delivery/order-detail?id=${orderId}&status=${status}` // ✅ 强制为 1
})
} else if (this.currentTask) {
uni.navigateTo({
url: `/pages/mall/delivery/order-detail?id=${this.currentTask.id}&status=${this.currentTask.status}`
})
}else{
uni.navigateTo({
url: `/pages/mall/delivery/order-detail?id=${orderId}&status=1` // ✅ 强制为 1
})
}
},
// 订单操作方法
async acceptOrder(orderId: string) {
try {
const ready = await Promise.race([supaReady, new Promise(resolve => setTimeout(() => resolve(false), 1500))])
if (!ready) console.warn('supaReady timeout/failed in acceptOrder - proceeding')
const driverId = this.driverInfo.id || null
if (!driverId) throw new Error('无配送员ID')
const res = await supa.from('ml_delivery_tasks').update({ driver_id: driverId, status: 2 }).eq('id', orderId).execute()
if (res && !res.error) {
uni.showToast({ title: '订单已接受', icon: 'success' })
await this.loadCurrentTask()
await this.loadAvailableOrders()
}
} catch (e) {
console.error('acceptOrder error', e)
uni.showToast({ title: '接受订单失败', icon: 'none' })
}
},
// 导航方法
goToOrderHistory() {
uni.navigateTo({
url: '/pages/mall/delivery/order-history'
})
},
// 跳转到“全部可接订单”页面
goToAllOrders() {
uni.navigateTo({
url: '/pages/mall/delivery/all'
})
},
goToEarnings() {
uni.navigateTo({
url: '/pages/mall/delivery/earnings'
})
},
goToProfile() {
uni.navigateTo({
url: '/pages/mall/delivery/profile'
})
},
goToSettings() {
uni.navigateTo({
url: '/pages/mall/delivery/settings'
})
}
}
}
</script>
<style>
/* ... 保持原有 style 部分不变 ... */
.delivery-container {
background-color: #f8f9fa;
min-height: 100vh;
padding-bottom: 40rpx;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.header {
background-color: #fff;
padding: 20rpx 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1rpx solid #e9ecef;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.driver-info {
display: flex;
align-items: center;
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
margin-right: 20rpx;
border: 2rpx solid #dee2e6;
}
.driver-details {
display: flex;
flex-direction: column;
justify-content: center;
}
.driver-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.work-status {
font-size: 24rpx;
padding: 6rpx 12rpx;
border-radius: 12rpx;
font-weight: 500;
}
.status-online {
background-color: #E8F5E8;
color: #4CAF50;
}
.status-offline {
background-color: #FFF3E0;
color: #FF9800;
}
.status-switch {
display: flex;
align-items: center;
gap: 10rpx;
}
.switch-label {
font-size: 22rpx;
color: #666;
}
/* 今日统计 */
.stats-section {
background-color: #fff;
margin: 20rpx;
padding: 20rpx 30rpx;
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
text-align: center;
}
.section-header-actions {
display: flex;
gap: 12rpx;
align-items: center;
}
.more-btn {
color: #1976d2;
font-size: 24rpx;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15rpx;
justify-items: center;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 15rpx;
background-color: #f8f9fa;
border-radius: 12rpx;
min-width: 120rpx;
}
.stat-value {
font-size: 36rpx;
font-weight: bold;
color: #4CAF50;
margin-bottom: 10rpx;
line-height: 1.2;
}
.stat-label {
font-size: 24rpx;
color: #666;
text-align: center;
}
/* 当前任务 */
.current-task-section {
background-color: #fff;
margin: 20rpx;
padding: 20rpx 30rpx;
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
text-align: center;
}
.task-card {
border: 1rpx solid #e9ecef;
border-radius: 12rpx;
padding: 20rpx;
background-color: #ffffff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15rpx;
padding-bottom: 15rpx;
border-bottom: 1rpx solid #f8f9fa;
}
.task-id {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.task-status {
font-size: 24rpx;
padding: 6rpx 12rpx;
border-radius: 12rpx;
font-weight: 500;
}
.task-accepted {
background-color: #E3F2FD;
color: #1976D2;
}
.task-picking {
background-color: #FFF3E0;
color: #F57C00;
}
.task-delivering {
background-color: #E8F5E8;
color: #388E3C;
}
.task-addresses {
margin-bottom: 20rpx;
}
.address-item {
display: flex;
align-items: flex-start;
margin-bottom: 15rpx;
padding: 10rpx 0;
border-bottom: 1rpx dashed #e9ecef;
}
.address-icon {
font-size: 28rpx;
margin-right: 15rpx;
margin-top: 5rpx;
color: #666;
}
.address-info {
display: flex;
flex-direction: column;
flex: 1;
}
.address-label {
font-size: 24rpx;
color: #666;
margin-bottom: 8rpx;
font-weight: 500;
}
.address-text {
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
word-break: break-all;
}
.contact-info {
font-size: 24rpx;
color: #666;
font-weight: 500;
}
.address-line {
width: 2rpx;
height: 30rpx;
background-color: #ddd;
margin: 10rpx 0 10rpx 14rpx;
}
.task-details {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
padding: 15rpx;
background-color: #f8f9fa;
border-radius: 8rpx;
font-size: 24rpx;
color: #666;
}
.task-info {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
font-size: 24rpx;
color: #666;
margin: 0 5rpx;
}
.task-actions {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
margin-top: 10rpx;
}
.action-btn {
flex: 1;
height: 80rpx;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
font-weight: 500;
padding: 0 10rpx;
box-sizing: border-box;
}
.primary {
background-color: #4CAF50;
color: #fff;
}
.secondary {
background-color: #f0f0f0;
color: #333;
border: 1rpx solid #ddd;
}
/* 可接取订单 */
.available-orders-section {
margin: 20rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.refresh-btn {
font-size: 26rpx;
color: #4CAF50;
padding: 8rpx 16rpx;
background-color: #e8f5e8;
border-radius: 12rpx;
font-weight: 500;
}
.empty-orders {
background-color: #fff;
padding: 40rpx 30rpx;
border-radius: 16rpx;
text-align: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.empty-text {
font-size: 32rpx;
color: #999;
margin-bottom: 15rpx;
}
.empty-subtitle {
font-size: 24rpx;
color: #ccc;
}
.order-card {
background-color: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 15rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
border: 1rpx solid #e9ecef;
}
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15rpx;
padding-bottom: 15rpx;
border-bottom: 1rpx solid #f8f9fa;
}
.order-id {
font-size: 28rpx;
color: #333;
font-weight: bold;
}
.order-fee {
font-size: 32rpx;
color: #4CAF50;
font-weight: bold;
}
.order-route {
display: flex;
align-items: center;
margin-bottom: 15rpx;
padding: 10rpx 0;
border-bottom: 1rpx solid #f8f9fa;
}
.route-item {
display: flex;
align-items: center;
flex: 1;
}
.route-icon {
font-size: 24rpx;
margin-right: 8rpx;
color: #666;
}
.route-text {
font-size: 26rpx;
color: #333;
word-break: break-all;
}
.route-arrow {
font-size: 24rpx;
color: #999;
margin: 0 15rpx;
}
.order-info {
display: flex;
justify-content: space-between;
margin-bottom: 15rpx;
padding: 10rpx 0;
border-bottom: 1rpx solid #f8f9fa;
font-size: 22rpx;
color: #666;
}
.info-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
font-size: 22rpx;
color: #666;
margin: 0 5rpx;
}
.order-actions {
display: flex;
gap: 10rpx;
margin-top: 10rpx;
}
.order-btn {
flex: 1;
height: 70rpx;
border-radius: 8rpx;
font-size: 26rpx;
border: none;
font-weight: 500;
padding: 0 10rpx;
box-sizing: border-box;
}
.accept {
background-color: #4CAF50;
color: #fff;
}
.detail {
background-color: #f0f0f0;
color: #333;
border: 1rpx solid #ddd;
}
/* 历史记录快捷入口 */
.quick-actions-section {
background-color: #fff;
margin: 20rpx;
padding: 20rpx 30rpx;
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.actions-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
justify-items: center;
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx;
background-color: #f8f9fa;
border-radius: 12rpx;
min-width: 120rpx;
cursor: pointer;
transition: background-color 0.2s;
}
.action-item:hover {
background-color: #e8f5e8;
}
.action-icon {
font-size: 48rpx;
margin-bottom: 15rpx;
color: #666;
}
.action-text {
font-size: 24rpx;
color: #333;
text-align: center;
font-weight: 500;
}
</style>