| | |
| | | <template> |
| | | <view class="container"> |
| | | <!-- 标题栏 --> |
| | | <Nav :title="formData.name" :customBack="formData.router" customType="navigateBack"></Nav> |
| | | <Nav title="行程历史" @back="goBack" /> |
| | | |
| | | <!-- 行程历史列表-可下拉滚动 --> |
| | | <scroll-view class="history-scroll" scroll-y="true" style="height: calc(100vh - 80rpx);"> |
| | | <view class="timeline-item" v-for="(item, index) in historyList" :key="index"> |
| | | <!-- 左侧时间轴 --> |
| | | <view class="timeline-left"> |
| | | <view class="timeline-dot"></view> |
| | | <view class="timeline-line" :class="{ last: index === historyList.length - 1 }"></view> |
| | | <view class="timeline-distance">{{ item.odometer }}km</view> |
| | | </view> |
| | | <!-- 加载状态 --> |
| | | <view v-if="loading && !list.length" class="center-state"> |
| | | <u-loading-icon size="32" text="加载中..." /> |
| | | </view> |
| | | |
| | | <!-- 右侧行程内容 --> |
| | | <view class="timeline-right"> |
| | | <view class="item-header"> |
| | | <view class="driver-action">{{ item.driverName }} {{ item.statusStr }}</view> |
| | | <view class="vehicle-info">{{ item.vehicleNumber }}</view> |
| | | <view class="location">{{ item.address }}</view> |
| | | <view class="time">{{ item.tripTime }}</view> |
| | | <!-- 空状态 --> |
| | | <view v-else-if="!loading && !list.length" class="center-state"> |
| | | <u-empty mode="history" text="暂无行程记录" /> |
| | | </view> |
| | | |
| | | <!-- 行程历史列表 --> |
| | | <scroll-view v-else class="history-scroll" scroll-y @scrolltolower="loadMore"> |
| | | <view class="timeline-list"> |
| | | <view |
| | | v-for="(item, index) in list" |
| | | :key="index" |
| | | class="timeline-item" |
| | | > |
| | | <!-- 左侧时间轴 --> |
| | | <view class="timeline-left"> |
| | | <view class="timeline-dot" :class="{ first: index === 0 }" /> |
| | | <view v-if="index < list.length - 1" class="timeline-line" /> |
| | | </view> |
| | | |
| | | <!-- 图片区域 --> |
| | | <view class="image-grid"> |
| | | <view class="image-item" v-for="(img, imgIndex) in item.voucherUrl" :key="imgIndex"> |
| | | <image class="img" :src="img" mode="aspectFill"></image> |
| | | <!-- 右侧行程卡片 --> |
| | | <view class="timeline-card"> |
| | | <view class="card-top"> |
| | | <view class="type-tag" :style="{ backgroundColor: item.tagBg, color: item.tagColor }"> |
| | | {{ item.statusStr }} |
| | | </view> |
| | | <text class="card-time">{{ item.tripTime }}</text> |
| | | </view> |
| | | |
| | | <view class="card-address"> |
| | | <u-icon name="map-fill" size="14" color="#909399" /> |
| | | <text class="address-text">{{ item.address || '暂无地址' }}</text> |
| | | </view> |
| | | |
| | | <view v-if="item.odometer" class="card-odometer"> |
| | | <u-icon name="car" size="14" color="#909399" /> |
| | | <text class="odometer-text">仪表里程:{{ item.odometer }} km</text> |
| | | </view> |
| | | |
| | | <view v-if="item.images && item.images.length" class="card-images"> |
| | | <image |
| | | v-for="(img, i) in item.images" |
| | | :key="i" |
| | | class="img-thumb" |
| | | :src="img" |
| | | mode="aspectFill" |
| | | lazy-load |
| | | @tap="onPreview(index, i)" |
| | | /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="load-more"> |
| | | <u-loadmore :status="loadStatus" /> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { tmsTripList, getcarType } from "@/common/examine"; |
| | | import { tmsTripListPage, getcarType } from '@/common/examine' |
| | | |
| | | const TYPE_COLORS = Object.freeze({ |
| | | '1': { bg: 'rgba(64,158,255,0.1)', color: '#409eff' }, |
| | | '0': { bg: 'rgba(103,194,58,0.1)', color: '#67c23a' }, |
| | | '2': { bg: 'rgba(230,162,60,0.1)', color: '#e6a23c' }, |
| | | '3': { bg: 'rgba(230,162,60,0.1)', color: '#e6a23c' }, |
| | | '4': { bg: 'rgba(144,147,153,0.1)', color: '#909399' }, |
| | | '5': { bg: 'rgba(144,147,153,0.1)', color: '#909399' }, |
| | | '6': { bg: 'rgba(245,108,108,0.1)', color: '#f56c6c' }, |
| | | '7': { bg: 'rgba(245,108,108,0.1)', color: '#f56c6c' }, |
| | | '8': { bg: 'rgba(103,194,58,0.1)', color: '#67c23a' }, |
| | | '100': { bg: 'rgba(192,196,204,0.1)', color: '#909399' }, |
| | | }) |
| | | const DEFAULT_COLOR = { bg: 'rgba(192,196,204,0.1)', color: '#c0c4cc' } |
| | | |
| | | export default { |
| | | data() { |
| | | return { |
| | | formData: {}, |
| | | historyList: [ |
| | | |
| | | ] |
| | | }; |
| | | }, |
| | | onLoad(options) { |
| | | this.formData = options; |
| | | console.log(options) |
| | | this.formData.router = options.router; |
| | | // 获取 URL 参数 |
| | | if (options.id) { |
| | | this.getList(); |
| | | list: [], |
| | | tripTypeDict: [], |
| | | loading: false, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | finished: false |
| | | } |
| | | }, |
| | | |
| | | computed: { |
| | | loadStatus() { |
| | | if (this.loading) return 'loading' |
| | | if (this.finished) return 'nomore' |
| | | return 'loadmore' |
| | | } |
| | | }, |
| | | |
| | | onLoad(options) { |
| | | this.formData = options || {} |
| | | this.initData() |
| | | }, |
| | | |
| | | methods: { |
| | | getList() { |
| | | getcarType('trip_type').then((res) => { |
| | | this.actionButtonRows = res |
| | | if (res.length > 0) { |
| | | tmsTripList(this.formData.id).then((res1) => { |
| | | this.historyList = res1; |
| | | this.historyList.forEach(item => { |
| | | // 查找匹配的dictLabel |
| | | const matchedDict = this.actionButtonRows.find(dictItem => dictItem.dictValue == item.tripType); |
| | | item.statusStr = matchedDict ? matchedDict.dictLabel : ''; |
| | | goBack() { |
| | | const pages = getCurrentPages() |
| | | for (let i = pages.length - 2; i >= 0; i--) { |
| | | if (pages[i].route && pages[i].route.includes('pages/examine')) { |
| | | uni.navigateBack({ delta: pages.length - 1 - i }) |
| | | return |
| | | } |
| | | } |
| | | uni.navigateBack({ delta: 1 }) |
| | | }, |
| | | |
| | | // 转换voucherUrl为数组 |
| | | if (item.voucherUrl) { |
| | | item.voucherUrl = item.voucherUrl.split(',').filter(url => url.trim() !== ''); |
| | | } else { |
| | | item.voucherUrl = []; |
| | | } |
| | | }); |
| | | async initData() { |
| | | this.loading = true |
| | | try { |
| | | const tripTypes = await getcarType('trip_type') |
| | | this.tripTypeDict = tripTypes || [] |
| | | await this.loadPage() |
| | | } catch { |
| | | uni.$u.toast('加载失败') |
| | | } finally { |
| | | this.loading = false |
| | | } |
| | | }, |
| | | |
| | | // 再处理odometer计算 |
| | | for (let i = 0; i < this.historyList.length; i++) { |
| | | if (i === 0) { |
| | | this.historyList[i].odometer = this.historyList[i].odometer; |
| | | } else { |
| | | let diff = this.historyList[i - 1].odometer - this.historyList[i].odometer; |
| | | // 负数处理为0,保留最多2位小数 |
| | | this.historyList[i].odometer = diff < 0 ? 0 : |
| | | (Number.isInteger(diff) ? diff : parseFloat(diff.toFixed(2))); |
| | | } |
| | | } |
| | | |
| | | }).catch(err => { |
| | | console.error('获取调度信息失败:', err); |
| | | }); |
| | | async loadPage() { |
| | | this.loading = true |
| | | try { |
| | | const params = { pageNum: this.pageNum, pageSize: this.pageSize } |
| | | // dispatchId 可选,不传查全部行程 |
| | | if (this.formData.id) { |
| | | params.dispatchId = this.formData.id |
| | | } |
| | | |
| | | const res = await tmsTripListPage(params) |
| | | |
| | | }).catch(err => { |
| | | }); |
| | | let rows, total |
| | | if (res && res.rows) { |
| | | rows = res.rows || [] |
| | | total = res.total || 0 |
| | | } else { |
| | | // 兼容非分页返回 |
| | | rows = Array.isArray(res) ? res : [] |
| | | total = rows.length |
| | | this.finished = true |
| | | } |
| | | |
| | | const processed = rows.map(item => { |
| | | const dict = this.tripTypeDict.find(d => d.dictValue == item.tripType) |
| | | const typeKey = String(item.tripType) |
| | | const colors = TYPE_COLORS[typeKey] || DEFAULT_COLOR |
| | | return { |
| | | ...item, |
| | | statusStr: dict?.dictLabel || '未知', |
| | | tagBg: colors.bg, |
| | | tagColor: colors.color, |
| | | images: item.voucherUrl |
| | | ? item.voucherUrl.split(',').filter(u => u.trim()) |
| | | : [] |
| | | } |
| | | }) |
| | | |
| | | if (this.pageNum === 1) { |
| | | this.list = processed |
| | | } else { |
| | | this.list = this.list.concat(processed) |
| | | } |
| | | |
| | | this.total = total |
| | | if (this.list.length >= total || rows.length < this.pageSize) { |
| | | this.finished = true |
| | | } |
| | | } catch { |
| | | uni.$u.toast('加载失败') |
| | | } finally { |
| | | this.loading = false |
| | | } |
| | | }, |
| | | |
| | | loadMore() { |
| | | if (this.loading || this.finished) return |
| | | this.pageNum++ |
| | | this.loadPage() |
| | | }, |
| | | |
| | | onPreview(itemIndex, imgIndex) { |
| | | const item = this.list[itemIndex] |
| | | if (item && item.images && item.images.length) { |
| | | uni.previewImage({ |
| | | urls: item.images, |
| | | current: item.images[imgIndex] |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | <style lang="scss" scoped> |
| | | .container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100vh; |
| | | background-color: #f7f7f7; |
| | | min-height: 100vh; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | /* 标题栏 */ |
| | | .title-bar { |
| | | height: 80rpx; |
| | | line-height: 80rpx; |
| | | text-align: center; |
| | | font-size: 32rpx; |
| | | font-weight: 500; |
| | | background-color: #fff; |
| | | border-bottom: 1rpx solid #ccc; |
| | | .center-state { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 60vh; |
| | | } |
| | | |
| | | /* 行程滚动区域 */ |
| | | .history-scroll { |
| | | flex: 1; |
| | | padding: 20rpx; |
| | | height: calc(100vh - 88rpx); |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | /* 单个行程项 */ |
| | | .timeline-item { |
| | | display: flex; |
| | | margin-bottom: 30rpx; |
| | | .timeline-list { |
| | | padding: 30rpx 24rpx 0; |
| | | } |
| | | |
| | | /* 左侧时间轴 */ |
| | | .timeline-item { |
| | | display: flex; |
| | | |
| | | &:last-child .timeline-line { |
| | | display: none; |
| | | } |
| | | } |
| | | |
| | | .timeline-left { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | width: 60rpx; |
| | | /* 固定宽度确保对齐 */ |
| | | margin-right: 20rpx; |
| | | width: 50rpx; |
| | | flex-shrink: 0; |
| | | padding-top: 30rpx; |
| | | } |
| | | |
| | | /* 时间轴圆点 */ |
| | | .timeline-dot { |
| | | width: 20rpx; |
| | | height: 20rpx; |
| | | border-radius: 50%; |
| | | background-color: #4285f4; |
| | | background-color: #dcdfe6; |
| | | border: 4rpx solid #fff; |
| | | box-shadow: 0 0 0 6rpx rgba(66, 133, 244, 0.1); |
| | | box-shadow: 0 0 0 4rpx rgba(0, 0, 0, 0.06); |
| | | flex-shrink: 0; |
| | | /* 防止压缩 */ |
| | | margin-top: 10rpx; |
| | | /* 上边距 */ |
| | | |
| | | &.first { |
| | | background-color: #4285f4; |
| | | box-shadow: 0 0 0 4rpx rgba(66, 133, 244, 0.25); |
| | | } |
| | | } |
| | | |
| | | /* 时间轴连接线 */ |
| | | .timeline-line { |
| | | width: 8rpx; |
| | | width: 3rpx; |
| | | flex: 1; |
| | | background-color: #eee; |
| | | margin: 4rpx 0; |
| | | /* 上下留出间距 */ |
| | | background-color: #e4e7ed; |
| | | min-height: 40rpx; |
| | | } |
| | | |
| | | /* 最后一个节点无连接线 */ |
| | | .timeline-line.last { |
| | | display: none; |
| | | .timeline-card { |
| | | flex: 1; |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | padding: 28rpx; |
| | | margin-left: 16rpx; |
| | | margin-bottom: 20rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04); |
| | | } |
| | | |
| | | /* 里程数 */ |
| | | .timeline-distance { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | white-space: nowrap; |
| | | margin-bottom: 10rpx; |
| | | /* 下边距 */ |
| | | flex-shrink: 0; |
| | | /* 防止压缩 */ |
| | | height: 30rpx; |
| | | /* 固定高度 */ |
| | | .card-top { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | /* 右侧行程内容 */ |
| | | .timeline-right { |
| | | flex: 1; |
| | | background-color: #fff; |
| | | border-radius: 12rpx; |
| | | padding: 24rpx; |
| | | box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | /* 行程头部信息 */ |
| | | .item-header { |
| | | margin-bottom: 16rpx; |
| | | } |
| | | |
| | | .driver-action { |
| | | font-size: 28rpx; |
| | | .type-tag { |
| | | padding: 6rpx 20rpx; |
| | | border-radius: 8rpx; |
| | | font-size: 24rpx; |
| | | font-weight: 500; |
| | | color: #333; |
| | | margin-bottom: 8rpx; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .vehicle-info, |
| | | .location, |
| | | .time { |
| | | .card-time { |
| | | font-size: 24rpx; |
| | | color: #909399; |
| | | } |
| | | |
| | | .card-address { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | gap: 10rpx; |
| | | margin-bottom: 12rpx; |
| | | } |
| | | |
| | | .address-text { |
| | | flex: 1; |
| | | font-size: 28rpx; |
| | | color: #303133; |
| | | line-height: 1.5; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .card-odometer { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10rpx; |
| | | margin-bottom: 12rpx; |
| | | } |
| | | |
| | | .odometer-text { |
| | | font-size: 26rpx; |
| | | color: #666; |
| | | margin-bottom: 6rpx; |
| | | color: #606266; |
| | | } |
| | | |
| | | /* 图片网格 */ |
| | | .image-grid { |
| | | .card-images { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10rpx; |
| | | gap: 12rpx; |
| | | margin-top: 20rpx; |
| | | padding-top: 20rpx; |
| | | border-top: 1rpx solid #f2f3f5; |
| | | } |
| | | |
| | | /* 图片项 */ |
| | | .image-item { |
| | | width: 23%; |
| | | padding-bottom: 23%; |
| | | /* 正方形比例 */ |
| | | position: relative; |
| | | .img-thumb { |
| | | width: 140rpx; |
| | | height: 140rpx; |
| | | border-radius: 10rpx; |
| | | border: 1rpx solid #ebeef5; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | /* 图片占位 */ |
| | | .img { |
| | | width: 100%; |
| | | height: 100%; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | border: 1rpx solid #eee; |
| | | border-radius: 4rpx; |
| | | .load-more { |
| | | padding: 30rpx 0 50rpx; |
| | | } |
| | | </style> |
| | | </style> |