All checks were successful
continuous-integration/drone/push Build is passing
414 lines
9.5 KiB
Vue
414 lines
9.5 KiB
Vue
<template>
|
||
<view class="my-taken-page">
|
||
<!-- 自定义导航栏 -->
|
||
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||
<view class="navbar-content">
|
||
<view class="nav-back" @click="goBack">
|
||
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
|
||
</view>
|
||
<text class="navbar-title">我的接单</text>
|
||
<view class="nav-placeholder"></view>
|
||
</view>
|
||
</view>
|
||
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
|
||
<!-- 筛选栏(吸顶) -->
|
||
<view class="filter-sticky" :style="{ top: (statusBarHeight + 44) + 'px' }">
|
||
<!-- 状态筛选标签 -->
|
||
<view class="tab-bar">
|
||
<scroll-view class="tab-scroll" scroll-x>
|
||
<view
|
||
class="tab-item"
|
||
v-for="tab in statusTabs"
|
||
:key="tab.value"
|
||
:class="{ active: currentStatus === tab.value }"
|
||
@click="switchStatus(tab.value)"
|
||
>{{ tab.label }}</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 类型筛选标签 -->
|
||
<view class="type-bar">
|
||
<scroll-view class="type-scroll" scroll-x>
|
||
<view
|
||
class="type-item"
|
||
v-for="tab in typeTabs"
|
||
:key="tab.value"
|
||
:class="{ active: currentType === tab.value }"
|
||
@click="switchType(tab.value)"
|
||
>{{ tab.label }}</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 订单列表 -->
|
||
<view class="order-list">
|
||
<view v-if="loading" class="loading-tip">加载中...</view>
|
||
<view v-else-if="filteredOrders.length === 0" class="empty-tip">暂无接单</view>
|
||
|
||
<view
|
||
v-for="order in filteredOrders"
|
||
:key="order.id"
|
||
class="order-card"
|
||
>
|
||
<view class="card-header">
|
||
<text class="order-type-tag">{{ getTypeLabel(order.orderType) }}</text>
|
||
<text class="order-status" :class="getStatusClass(order.status)">{{ getStatusLabel(order.status) }}</text>
|
||
</view>
|
||
|
||
<view class="card-body">
|
||
<view class="info-row">
|
||
<text class="info-label">订单编号</text>
|
||
<text class="info-value">{{ order.orderNo }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="order.itemName">
|
||
<text class="info-label">物品</text>
|
||
<text class="info-value">{{ order.itemName }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="order.deliveryLocation">
|
||
<text class="info-label">送达地点</text>
|
||
<text class="info-value">{{ order.deliveryLocation }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">跑腿佣金</text>
|
||
<text class="info-value price">¥{{ order.commission }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">接单时间</text>
|
||
<text class="info-value">{{ formatTime(order.acceptedAt) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 操作按钮 -->
|
||
<view class="card-footer">
|
||
<!-- 进行中:完成订单 + 查看详情 + 联系单主 -->
|
||
<template v-if="order.status === 'InProgress'">
|
||
<view class="btn btn-primary" @click="goComplete(order)">
|
||
<text>完成订单</text>
|
||
</view>
|
||
<view class="btn btn-contact" @click="goChat(order)">
|
||
<text>联系单主</text>
|
||
</view>
|
||
<view class="btn btn-detail" @click="goDetail(order)">
|
||
<text>查看详情</text>
|
||
</view>
|
||
</template>
|
||
|
||
<!-- 已完成:查看详情 + 联系单主 -->
|
||
<template v-else-if="order.status === 'Completed'">
|
||
<view class="btn btn-contact" @click="goChat(order)">
|
||
<text>联系单主</text>
|
||
</view>
|
||
<view class="btn btn-detail" @click="goDetail(order)">
|
||
<text>查看详情</text>
|
||
</view>
|
||
</template>
|
||
|
||
<!-- 其他状态:查看详情 -->
|
||
<template v-else>
|
||
<view class="btn btn-detail" @click="goDetail(order)">
|
||
<text>查看详情</text>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { getMyTaken } from '../../utils/api'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
statusTabs: [
|
||
{ label: '全部', value: '' },
|
||
{ label: '进行中', value: 'InProgress' },
|
||
{ label: '待确认', value: 'WaitConfirm' },
|
||
{ label: '已完成', value: 'Completed' },
|
||
{ label: '已取消', value: 'Cancelled' }
|
||
],
|
||
typeTabs: [
|
||
{ label: '全部', value: '' },
|
||
{ label: '代取', value: 'Pickup' },
|
||
{ label: '代送', value: 'Delivery' },
|
||
{ label: '万能帮', value: 'Help' },
|
||
{ label: '代购', value: 'Purchase' },
|
||
{ label: '美食街', value: 'Food' }
|
||
],
|
||
currentStatus: '',
|
||
currentType: '',
|
||
orders: [],
|
||
loading: false,
|
||
statusBarHeight: 0
|
||
}
|
||
},
|
||
computed: {
|
||
filteredOrders() {
|
||
return this.orders.filter(o => {
|
||
if (this.currentStatus) {
|
||
const statuses = this.currentStatus.split(',')
|
||
if (!statuses.includes(o.status)) return false
|
||
}
|
||
if (this.currentType && o.orderType !== this.currentType) return false
|
||
return true
|
||
})
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
const sysInfo = uni.getSystemInfoSync()
|
||
this.statusBarHeight = sysInfo.statusBarHeight || 0
|
||
if (options.status) {
|
||
this.currentStatus = options.status
|
||
}
|
||
},
|
||
onShow() {
|
||
this.loadOrders()
|
||
},
|
||
methods: {
|
||
goBack() { uni.navigateBack() },
|
||
/** 加载我的接单 */
|
||
async loadOrders() {
|
||
this.loading = true
|
||
try {
|
||
const res = await getMyTaken({})
|
||
this.orders = res?.items || res || []
|
||
} catch (e) {
|
||
this.orders = []
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
|
||
switchStatus(value) { this.currentStatus = value },
|
||
switchType(value) { this.currentType = value },
|
||
|
||
getTypeLabel(type) {
|
||
const map = { Pickup: '代取', Delivery: '代送', Help: '万能帮', Purchase: '代购', Food: '美食街' }
|
||
return map[type] || type
|
||
},
|
||
|
||
getStatusLabel(status) {
|
||
const map = { InProgress: '进行中', Completed: '已完成', Cancelled: '已取消', WaitConfirm: '待确认', Appealing: '申诉中' }
|
||
return map[status] || status
|
||
},
|
||
|
||
getStatusClass(status) {
|
||
const map = { InProgress: 'status-progress', Completed: 'status-done', Cancelled: 'status-cancel', WaitConfirm: 'status-confirm', Appealing: 'status-appeal' }
|
||
return map[status] || ''
|
||
},
|
||
|
||
formatTime(dateStr) {
|
||
if (!dateStr) return '-'
|
||
const d = new Date(typeof dateStr === 'string' && !dateStr.endsWith('Z') ? dateStr + 'Z' : dateStr)
|
||
const pad = (n) => String(n).padStart(2, '0')
|
||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||
},
|
||
|
||
/** 跳转完成订单确认页 */
|
||
goComplete(order) {
|
||
uni.navigateTo({ url: `/pages/order/complete-order?id=${order.id}` })
|
||
},
|
||
|
||
/** 跳转聊天页 */
|
||
goChat(order) {
|
||
uni.navigateTo({ url: `/pages/message/chat?orderId=${order.id}` })
|
||
},
|
||
|
||
/** 跳转订单详情 */
|
||
goDetail(order) {
|
||
uni.navigateTo({ url: `/pages/order/order-detail?id=${order.id}` })
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 自定义导航栏 */
|
||
.custom-navbar {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
z-index: 999;
|
||
background: #FFB700;
|
||
}
|
||
.navbar-content {
|
||
height: 44px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 20rpx;
|
||
}
|
||
.nav-back {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.back-icon {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
.navbar-title {
|
||
font-size: 34rpx;
|
||
font-weight: bold;
|
||
color: #363636;
|
||
}
|
||
.nav-placeholder {
|
||
width: 60rpx;
|
||
}
|
||
.my-taken-page {
|
||
background-color: #f5f5f5;
|
||
min-height: 100vh;
|
||
padding-bottom: 40rpx;
|
||
}
|
||
|
||
/* 筛选栏吸顶容器 */
|
||
.filter-sticky {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 99;
|
||
background-color: #ffffff;
|
||
}
|
||
|
||
.tab-bar {
|
||
padding: 16rpx 24rpx;
|
||
}
|
||
|
||
.tab-scroll {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.tab-item {
|
||
display: inline-block;
|
||
padding: 10rpx 24rpx;
|
||
margin-right: 12rpx;
|
||
font-size: 26rpx;
|
||
color: #666666;
|
||
border-radius: 28rpx;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.tab-item.active {
|
||
background-color: #FFB700;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.type-bar {
|
||
padding: 12rpx 24rpx;
|
||
border-top: 1rpx solid #f0f0f0;
|
||
}
|
||
|
||
.type-scroll {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.type-item {
|
||
display: inline-block;
|
||
padding: 8rpx 20rpx;
|
||
margin-right: 12rpx;
|
||
font-size: 24rpx;
|
||
color: #999999;
|
||
border-radius: 24rpx;
|
||
border: 1rpx solid #e0e0e0;
|
||
}
|
||
|
||
.type-item.active {
|
||
color: #FFB700;
|
||
border-color: #FFB700;
|
||
}
|
||
|
||
.order-list {
|
||
padding: 16rpx 24rpx;
|
||
}
|
||
|
||
.loading-tip, .empty-tip {
|
||
text-align: center;
|
||
color: #999999;
|
||
font-size: 28rpx;
|
||
padding: 80rpx 0;
|
||
}
|
||
|
||
.order-card {
|
||
background-color: #ffffff;
|
||
border-radius: 16rpx;
|
||
padding: 24rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.order-type-tag {
|
||
font-size: 24rpx;
|
||
color: #ffffff;
|
||
background-color: #FFB700;
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.order-status { font-size: 24rpx; font-weight: 500; }
|
||
.status-progress { color: #FFB700; }
|
||
.status-done { color: #52c41a; }
|
||
.status-cancel { color: #999999; }
|
||
.status-confirm { color: #ff9900; }
|
||
.status-appeal { color: #e64340; }
|
||
|
||
.card-body { margin-bottom: 16rpx; }
|
||
|
||
.info-row {
|
||
display: flex;
|
||
padding: 6rpx 0;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 26rpx;
|
||
color: #999999;
|
||
width: 140rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 26rpx;
|
||
color: #333333;
|
||
flex: 1;
|
||
}
|
||
|
||
.info-value.price {
|
||
color: #e64340;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.card-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 16rpx;
|
||
padding-top: 16rpx;
|
||
border-top: 1rpx solid #f0f0f0;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.btn {
|
||
padding: 12rpx 28rpx;
|
||
border-radius: 32rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.btn text { font-size: 26rpx; }
|
||
|
||
.btn-primary { background-color: #FAD146; }
|
||
.btn-primary text { color: #333333; }
|
||
|
||
.btn-detail { border: 1rpx solid #FFB700; }
|
||
.btn-detail text { color: #FFB700; }
|
||
|
||
.btn-contact { border: 1rpx solid #52c41a; }
|
||
.btn-contact text { color: #52c41a; }
|
||
</style>
|