<template>
|
<view class="container">
|
<!-- 自定义导航栏 -->
|
<u-navbar
|
:placeholder="true"
|
bg-color="#409eff"
|
title="被指派行程"
|
:title-style="{ color: '#fff', fontSize: '16px', fontWeight: 500 }"
|
>
|
<view slot="left" class="nav-left-slot" @click="toggleSidebar">
|
<u-icon name="list" color="#fff" size="22" />
|
</view>
|
</u-navbar>
|
|
<!-- 主要内容区 -->
|
<view class="main-content">
|
<!-- 下拉刷新容器 -->
|
<scroll-view
|
class="task-scroll"
|
scroll-y
|
:refresher-enabled="true"
|
:refresher-triggered="isRefreshing"
|
@refresherrefresh="onRefresh"
|
@scrolltolower="onLoadMore"
|
:lower-threshold="100"
|
>
|
<view class="scroll-content">
|
<!-- 加载骨架屏 -->
|
<view v-if="loading && !taskList.length">
|
<view v-for="i in 3" :key="i" class="skeleton-item">
|
<u-skeleton :rows="4" :title="false" avatar avatar-size="0" />
|
</view>
|
</view>
|
|
<!-- 空状态 -->
|
<view v-else-if="!taskList.length" class="empty-state">
|
<u-empty mode="list" text="暂无被指派行程" />
|
</view>
|
|
<!-- 行程列表 -->
|
<view v-else>
|
<view
|
v-for="item in taskList"
|
:key="item.dispatchId"
|
class="task-card"
|
@click="handleTaskClick(item)"
|
>
|
<!-- 卡片头部:单号和状态 -->
|
<view class="card-header">
|
<view class="dispatch-no">
|
<text class="label">调度单号</text>
|
<text class="value">{{ item.dispatchNo }}</text>
|
</view>
|
<view class="status-tag" :class="item.statusClass">
|
{{ item.statusStr }}
|
</view>
|
</view>
|
|
<!-- 卡片主体 -->
|
<view class="card-body">
|
<view class="info-row">
|
<u-icon name="car" size="16" color="#909399" />
|
<text class="info-text">{{ item.licensePlate || '未分配车辆' }}</text>
|
</view>
|
|
<view class="route-section">
|
<view class="route-line">
|
<view class="route-dot start" />
|
<view class="route-path" />
|
<view class="route-dot end" />
|
</view>
|
<view class="route-info">
|
<text class="route-text">{{ item.transportLine || '暂无路线信息' }}</text>
|
</view>
|
</view>
|
|
<view class="info-row">
|
<u-icon name="account" size="16" color="#909399" />
|
<text class="info-text">{{ item.customerName || '未知客户' }}</text>
|
</view>
|
|
<view class="info-row time-row">
|
<u-icon name="clock" size="16" color="#f0ae2d" />
|
<text class="time-text">最晚发车:{{ item.latestDeparture }}</text>
|
</view>
|
</view>
|
</view>
|
|
<!-- 加载更多 -->
|
<view class="load-more">
|
<u-loading-icon v-if="loadingMore" size="16" text="加载中..." />
|
<text v-else-if="!hasMore" class="no-more">没有更多了</text>
|
</view>
|
</view>
|
</view>
|
</scroll-view>
|
</view>
|
|
<!-- 底部操作栏 -->
|
<view class="bottom-bar">
|
<view class="bottom-btn" @click="goToHistory">
|
<u-icon name="order" size="18" color="#409eff" />
|
<text>历史调度单</text>
|
</view>
|
<view class="divider" />
|
<view class="bottom-btn" @click="goToPaymentList">
|
<u-icon name="red-packet" size="18" color="#ff6b6b" />
|
<text>垫付列表</text>
|
</view>
|
</view>
|
|
<!-- 侧边栏弹窗 -->
|
<u-popup
|
:show="showSidebar"
|
mode="left"
|
:overlay-opacity="0.3"
|
@close="closeSidebar"
|
>
|
<view class="sidebar-content">
|
<view class="sidebar-header">
|
<image src="/static/oss.png" class="logo" mode="aspectFit" />
|
<text class="company-name">智慧物流调度平台</text>
|
</view>
|
|
<view class="sidebar-menu">
|
<view class="menu-item" @click="handleLogout">
|
<text class="menu-text">退出登录</text>
|
</view>
|
</view>
|
</view>
|
</u-popup>
|
</view>
|
</template>
|
|
<script>
|
import { getSsignedItineraryList } from '@/common/beReferred'
|
import { navigateTo, PAGES } from '@/common/router'
|
|
// 状态映射配置
|
const STATUS_MAP = {
|
'待甩挂': { class: 'status-pending', color: '#ff6b6b' },
|
'待接挂': { class: 'status-pending', color: '#ff6b6b' },
|
'运输中': { class: 'status-transporting', color: '#409eff' },
|
'已完成': { class: 'status-completed', color: '#67c23a' },
|
'已取消': { class: 'status-cancelled', color: '#909399' }
|
}
|
|
export default {
|
data() {
|
return {
|
taskList: [],
|
loading: false,
|
loadingMore: false,
|
isRefreshing: false,
|
showSidebar: false,
|
pageNum: 1,
|
pageSize: 10,
|
hasMore: true,
|
_isFirstLoad: true
|
}
|
},
|
|
onLoad() {
|
this._isFirstLoad = true
|
this.fetchTaskList()
|
},
|
|
onShow() {
|
// 首次加载时跳过(onLoad 已经调用过了)
|
if (this._isFirstLoad) {
|
this._isFirstLoad = false
|
return
|
}
|
// 从其他页面返回时刷新数据
|
this.pageNum = 1
|
this.fetchTaskList(true)
|
},
|
|
onPullDownRefresh() {
|
this.onRefresh()
|
},
|
|
onReachBottom() {
|
this.onLoadMore()
|
},
|
|
methods: {
|
// 格式化时间
|
formatTime(timeStr) {
|
if (!timeStr) return '暂无'
|
|
const date = new Date(timeStr.replace(/-/g, '/'))
|
if (isNaN(date.getTime())) return timeStr
|
|
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
const day = date.getDate().toString().padStart(2, '0')
|
const hours = date.getHours().toString().padStart(2, '0')
|
const minutes = date.getMinutes().toString().padStart(2, '0')
|
|
return `${month}-${day} ${hours}:${minutes}`
|
},
|
|
// 加载数据
|
async fetchTaskList(isRefresh = false) {
|
if (this.loading && !isRefresh) return
|
|
this.loading = true
|
|
try {
|
const res = await getSsignedItineraryList({
|
pageNum: this.pageNum,
|
pageSize: this.pageSize
|
})
|
|
const list = (res || []).map(item => ({
|
...item,
|
statusClass: STATUS_MAP[item.statusStr]?.class || 'status-default'
|
}))
|
|
if (isRefresh) {
|
this.taskList = list
|
uni.stopPullDownRefresh()
|
this.isRefreshing = false
|
} else {
|
this.taskList = [...this.taskList, ...list]
|
}
|
|
this.hasMore = list.length === this.pageSize
|
} catch (error) {
|
console.error('获取行程列表失败:', error)
|
uni.$u.toast('获取数据失败,请重试')
|
} finally {
|
this.loading = false
|
this.loadingMore = false
|
}
|
},
|
|
// 下拉刷新
|
onRefresh() {
|
this.isRefreshing = true
|
this.pageNum = 1
|
this.fetchTaskList(true)
|
},
|
|
// 加载更多
|
onLoadMore() {
|
if (!this.hasMore || this.loadingMore) return
|
|
this.loadingMore = true
|
this.pageNum++
|
this.fetchTaskList()
|
},
|
|
// 跳转到历史记录
|
goToHistory() {
|
navigateTo(PAGES.HISTORY)
|
},
|
|
// 跳转到垫付列表
|
goToPaymentList() {
|
navigateTo(PAGES.PAYMENT_LIST)
|
},
|
|
// 任务点击
|
handleTaskClick(item) {
|
// 根据状态决定跳转页面
|
if (['待甩挂', '待接挂'].includes(item.statusStr)) {
|
navigateTo(PAGES.TRANSPORTATION, {
|
id: item.dispatchId,
|
statusStr: item.statusStr,
|
name: '上传行程'
|
})
|
} else {
|
// 直接跳转到操作页面,避免路由分发导致的闪烁
|
uni.navigateTo({
|
url: `/pages/examine/operate?id=${item.dispatchId}&name=上传行程`
|
})
|
}
|
},
|
|
// 切换侧边栏
|
toggleSidebar() {
|
this.showSidebar = !this.showSidebar
|
},
|
|
// 关闭侧边栏
|
closeSidebar() {
|
this.showSidebar = false
|
},
|
|
// 退出登录
|
handleLogout() {
|
uni.showModal({
|
title: '提示',
|
content: '确定要退出登录吗?',
|
success: (res) => {
|
if (res.confirm) {
|
// 显示加载提示
|
uni.showLoading({
|
title: '退出中...',
|
mask: true
|
})
|
|
// 清除登录状态
|
uni.$u.vuex('sso_user_token', undefined)
|
uni.$u.vuex('user_info', undefined)
|
|
// 延迟跳转,避免白屏
|
setTimeout(() => {
|
uni.hideLoading()
|
uni.reLaunch({
|
url: '/pages/login/index'
|
})
|
}, 300)
|
}
|
this.closeSidebar()
|
}
|
})
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
// 变量定义
|
$primary-color: #409eff;
|
$success-color: #67c23a;
|
$warning-color: #e6a23c;
|
$danger-color: #f56c6c;
|
$info-color: #909399;
|
$bg-color: #f5f7fa;
|
$card-bg: #ffffff;
|
$text-primary: #303133;
|
$text-regular: #606266;
|
$text-secondary: #909399;
|
|
// 布局
|
.container {
|
min-height: 100vh;
|
background-color: $bg-color;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.main-content {
|
flex: 1;
|
overflow: hidden;
|
padding-bottom: 100rpx;
|
}
|
|
// 导航栏
|
.nav-left-slot {
|
padding: 0 20rpx;
|
display: flex;
|
align-items: center;
|
}
|
|
// 滚动区域
|
.task-scroll {
|
height: calc(100vh - 44px - 100rpx); // 减去导航栏高度(44px)和底部栏高度(100rpx)
|
box-sizing: border-box;
|
}
|
|
.scroll-content {
|
padding: 20rpx;
|
min-height: 100%;
|
}
|
|
// 骨架屏
|
.skeleton-item {
|
background: $card-bg;
|
border-radius: 16rpx;
|
padding: 30rpx;
|
margin-bottom: 20rpx;
|
}
|
|
// 空状态
|
.empty-state {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
height: 60vh;
|
}
|
|
// 任务卡片
|
.task-card {
|
background: $card-bg;
|
border-radius: 16rpx;
|
padding: 30rpx;
|
margin-bottom: 20rpx;
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
&:active {
|
transform: scale(0.98);
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
}
|
}
|
|
// 卡片头部
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding-bottom: 20rpx;
|
border-bottom: 1rpx solid #ebeef5;
|
margin-bottom: 20rpx;
|
}
|
|
.dispatch-no {
|
display: flex;
|
flex-direction: column;
|
|
.label {
|
font-size: 22rpx;
|
color: $text-secondary;
|
margin-bottom: 4rpx;
|
}
|
|
.value {
|
font-size: 30rpx;
|
font-weight: 600;
|
color: $text-primary;
|
font-family: 'Courier New', monospace;
|
}
|
}
|
|
// 状态标签
|
.status-tag {
|
padding: 8rpx 20rpx;
|
border-radius: 8rpx;
|
font-size: 24rpx;
|
font-weight: 500;
|
}
|
|
.status-pending {
|
background-color: rgba($danger-color, 0.1);
|
color: $danger-color;
|
}
|
|
.status-transporting {
|
background-color: rgba($primary-color, 0.1);
|
color: $primary-color;
|
}
|
|
.status-completed {
|
background-color: rgba($success-color, 0.1);
|
color: $success-color;
|
}
|
|
.status-cancelled {
|
background-color: rgba($info-color, 0.1);
|
color: $info-color;
|
}
|
|
.status-default {
|
background-color: rgba($info-color, 0.1);
|
color: $info-color;
|
}
|
|
// 卡片主体
|
.card-body {
|
display: flex;
|
flex-direction: column;
|
gap: 16rpx;
|
}
|
|
.info-row {
|
display: flex;
|
align-items: center;
|
gap: 12rpx;
|
}
|
|
.info-text {
|
font-size: 28rpx;
|
color: $text-regular;
|
}
|
|
.time-row {
|
margin-top: 8rpx;
|
padding-top: 16rpx;
|
border-top: 1rpx dashed #ebeef5;
|
}
|
|
.time-text {
|
font-size: 26rpx;
|
color: $warning-color;
|
font-weight: 500;
|
}
|
|
// 路线展示
|
.route-section {
|
display: flex;
|
align-items: center;
|
gap: 20rpx;
|
padding: 16rpx 0;
|
}
|
|
.route-line {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
width: 24rpx;
|
}
|
|
.route-dot {
|
width: 16rpx;
|
height: 16rpx;
|
border-radius: 50%;
|
|
&.start {
|
background-color: $success-color;
|
box-shadow: 0 0 8rpx rgba($success-color, 0.4);
|
}
|
|
&.end {
|
background-color: $danger-color;
|
box-shadow: 0 0 8rpx rgba($danger-color, 0.4);
|
}
|
}
|
|
.route-path {
|
width: 2rpx;
|
height: 40rpx;
|
background: linear-gradient(to bottom, $success-color, $danger-color);
|
margin: 8rpx 0;
|
}
|
|
.route-info {
|
flex: 1;
|
}
|
|
.route-text {
|
font-size: 28rpx;
|
color: $text-primary;
|
font-weight: 500;
|
line-height: 1.5;
|
}
|
|
// 加载更多
|
.load-more {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
padding: 30rpx 0;
|
|
.no-more {
|
font-size: 24rpx;
|
color: $text-secondary;
|
}
|
}
|
|
// 底部栏
|
.bottom-bar {
|
position: fixed;
|
bottom: 0;
|
left: 0;
|
right: 0;
|
height: 100rpx;
|
background: $card-bg;
|
display: flex;
|
align-items: center;
|
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
|
z-index: 100;
|
}
|
|
.bottom-btn {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
align-items: center;
|
gap: 8rpx;
|
height: 100%;
|
transition: background-color 0.2s;
|
|
&:active {
|
background-color: rgba($primary-color, 0.05);
|
}
|
|
text {
|
font-size: 24rpx;
|
color: $text-regular;
|
}
|
}
|
|
.divider {
|
width: 1rpx;
|
height: 50rpx;
|
background-color: #ebeef5;
|
}
|
|
// 侧边栏
|
.sidebar-content {
|
width: 500rpx;
|
height: 100vh;
|
background: $card-bg;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.sidebar-header {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
padding: 240rpx 40rpx 60rpx;
|
|
.logo {
|
width: 160rpx;
|
height: 160rpx;
|
border-radius: 20rpx;
|
margin-bottom: 24rpx;
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
|
}
|
|
.company-name {
|
font-size: 32rpx;
|
color: $primary-color;
|
font-weight: 600;
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
}
|
}
|
|
.sidebar-menu {
|
flex: 1;
|
padding: 40rpx 0;
|
position: absolute;
|
bottom: 65px;
|
left: 50%;
|
transform: translateX(-50%);
|
}
|
.menu-item {
|
display: flex;
|
align-items: center;
|
gap: 20rpx;
|
padding: 30rpx 40rpx;
|
transition: background-color 0.2s;
|
|
&:active {
|
background-color: rgba($warning-color, 0.1);
|
}
|
|
.menu-text {
|
font-size: 30rpx;
|
color: $warning-color;
|
}
|
}
|
</style>
|