Files
akmon/pages/mall/delivery/index.uvue
2026-01-20 08:04:15 +08:00

924 lines
19 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="confirmDelivery">确认送达</button>
<button class="action-btn secondary" @click="contactCustomer">联系客户</button>
<button class="action-btn secondary" @click="viewNavigation">查看导航</button>
</view>
</view>
</view>
<!-- 可接取订单 -->
<view v-if="!currentTask && isOnline" class="available-orders-section">
<view class="section-header">
<text class="section-title">附近订单</text>
<text class="refresh-btn" @click="refreshOrders">🔄 刷新</text>
</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'
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: 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>
}
},
onLoad() {
this.loadDriverInfo()
this.loadTodayStats()
this.loadCurrentTask()
this.loadAvailableOrders()
},
onShow() {
// 页面显示时刷新数据
this.refreshData()
},
methods: {
// 加载配送员信息
loadDriverInfo() {
// TODO: 调用API获取配送员信息
this.driverInfo.real_name = '张师傅'
this.driverInfo.rating = 4.8
this.driverInfo.total_orders = 1250
},
// 加载今日统计
loadTodayStats() {
// TODO: 调用API获取今日统计
this.todayStats = {
completed_orders: 8,
total_earning: '245.60',
total_distance: 45,
avg_rating: 4.9
}
},
// 加载当前任务
loadCurrentTask() {
// TODO: 调用API获取当前任务
this.currentTask = {
id: '1',
order_no: 'D202501081234',
status: 2,
pickup_address: {
detail: '华强北商业区华强电子世界2楼A205',
area: '华强北'
},
delivery_address: {
detail: '南山区科技园深南大道9999号',
area: '科技园'
},
pickup_contact: {
name: '商家联系人',
phone: '138****5678'
},
delivery_contact: {
name: '张先生',
phone: '139****1234'
},
delivery_fee: 8.5,
distance: 12.5,
estimated_time: 35,
created_at: '2025-01-08T14:30:00Z'
}
},
// 加载可接取订单
loadAvailableOrders() {
if (!this.isOnline || this.currentTask) {
this.availableOrders = []
return
}
// TODO: 调用API获取附近订单
this.availableOrders = [
{
id: '2',
order_no: 'D202501081235',
pickup_address: {
detail: '福田区购物公园',
area: '购物公园'
},
delivery_address: {
detail: '南山区海岸城',
area: '海岸城'
},
delivery_fee: 12.0,
distance: 8.2,
estimated_time: 25,
created_at: '2025-01-08T15:00:00Z'
}
]
},
// 刷新数据
refreshData() {
this.loadTodayStats()
this.loadCurrentTask()
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)}小时前`
}
},
// 任务操作方法
acceptTask() {
// TODO: 调用API接受任务
uni.showToast({
title: '任务已接受',
icon: 'success'
})
},
startPickup() {
// TODO: 调用API开始取货
uni.showToast({
title: '开始取货',
icon: 'success'
})
},
confirmPickup() {
// TODO: 调用API确认取货
uni.showToast({
title: '取货完成',
icon: 'success'
})
},
startDelivery() {
// TODO: 调用API开始配送
uni.showToast({
title: '开始配送',
icon: 'success'
})
},
confirmDelivery() {
// TODO: 调用API确认送达
uni.showToast({
title: '配送完成',
icon: 'success'
})
this.currentTask = null
this.loadAvailableOrders()
},
contactCustomer() {
if (this.currentTask) {
uni.makePhoneCall({
phoneNumber: this.currentTask.delivery_contact.phone
})
}
},
viewNavigation() {
// TODO: 打开地图导航
uni.showToast({
title: '打开导航',
icon: 'none'
})
},
// 订单操作方法
acceptOrder(orderId: string) {
// TODO: 调用API接受订单
uni.showToast({
title: '订单已接受',
icon: 'success'
})
this.loadCurrentTask()
this.loadAvailableOrders()
},
viewOrderDetail(orderId: string) {
uni.navigateTo({
url: `/pages/mall/delivery/order-detail?id=${orderId}`
})
},
// 导航方法
goToOrderHistory() {
uni.navigateTo({
url: '/pages/mall/delivery/order-history'
})
},
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>
.delivery-container {
background-color: #f5f5f5;
min-height: 100vh;
padding-bottom: 40rpx;
}
.header {
background-color: #fff;
padding: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1rpx solid #e5e5e5;
}
.driver-info {
display: flex;
align-items: center;
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
margin-right: 20rpx;
}
.driver-details {
display: flex;
flex-direction: column;
}
.driver-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.work-status {
font-size: 24rpx;
padding: 6rpx 12rpx;
border-radius: 12rpx;
}
.status-online {
background-color: #E8F5E8;
color: #4CAF50;
}
.status-offline {
background-color: #FFF3E0;
color: #FF9800;
}
.status-switch {
display: flex;
flex-direction: column;
align-items: center;
}
.switch-label {
font-size: 22rpx;
color: #666;
margin-top: 8rpx;
}
.stats-section {
background-color: #fff;
margin: 20rpx;
padding: 30rpx;
border-radius: 16rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 30rpx;
}
.stats-grid {
display: flex;
justify-content: space-between;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.stat-value {
font-size: 36rpx;
font-weight: bold;
color: #4CAF50;
margin-bottom: 10rpx;
}
.stat-label {
font-size: 24rpx;
color: #666;
}
.current-task-section {
background-color: #fff;
margin: 20rpx;
padding: 30rpx;
border-radius: 16rpx;
}
.task-card {
border: 1rpx solid #e5e5e5;
border-radius: 12rpx;
padding: 20rpx;
}
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 15rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.task-id {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.task-status {
font-size: 24rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
}
.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;
}
.address-icon {
font-size: 28rpx;
margin-right: 15rpx;
margin-top: 5rpx;
}
.address-info {
display: flex;
flex-direction: column;
flex: 1;
}
.address-label {
font-size: 24rpx;
color: #666;
margin-bottom: 8rpx;
}
.address-text {
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
}
.contact-info {
font-size: 24rpx;
color: #666;
}
.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;
}
.task-info {
font-size: 24rpx;
color: #666;
}
.task-actions {
display: flex;
flex-wrap: wrap;
gap: 15rpx;
}
.action-btn {
flex: 1;
height: 80rpx;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
}
.primary {
background-color: #4CAF50;
color: #fff;
}
.secondary {
background-color: #f0f0f0;
color: #333;
}
.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;
}
.empty-orders {
background-color: #fff;
padding: 80rpx 30rpx;
border-radius: 16rpx;
text-align: center;
}
.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;
}
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15rpx;
}
.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;
}
.route-item {
display: flex;
align-items: center;
flex: 1;
}
.route-icon {
font-size: 24rpx;
margin-right: 8rpx;
}
.route-text {
font-size: 26rpx;
color: #333;
}
.route-arrow {
font-size: 24rpx;
color: #999;
margin: 0 15rpx;
}
.order-info {
display: flex;
justify-content: space-between;
margin-bottom: 15rpx;
}
.info-item {
font-size: 22rpx;
color: #666;
}
.order-actions {
display: flex;
gap: 15rpx;
}
.order-btn {
flex: 1;
height: 70rpx;
border-radius: 8rpx;
font-size: 26rpx;
border: none;
}
.accept {
background-color: #4CAF50;
color: #fff;
}
.detail {
background-color: #f0f0f0;
color: #333;
}
.quick-actions-section {
background-color: #fff;
margin: 20rpx;
padding: 30rpx;
border-radius: 16rpx;
}
.actions-grid {
display: flex;
justify-content: space-between;
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.action-icon {
font-size: 48rpx;
margin-bottom: 15rpx;
}
.action-text {
font-size: 24rpx;
color: #333;
text-align: center;
}
</style>