Files
frontend-app/pages/rwa_package/express/express.vue
T

441 lines
10 KiB
Vue
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.
<template>
<view class="content">
<TopSafe></TopSafe>
<Header title="物流信息" />
<view class="main-body">
<!-- 多物流 Tab 切换 (数据源:this.expressList) -->
<view class="tabs-bar" v-if="expressList && expressList.length > 0">
<scroll-view class="tabs-scroll" scroll-x :show-scrollbar="false">
<view class="tabs-wrapper">
<view
v-for="(item, index) in expressList"
:key="index"
:class="['tab-item', { active: currentTab === index }]"
@click="onSelectTab(index)"
>
{{ getTabName(index) }}
</view>
</view>
</scroll-view>
</view>
<!-- 物流详情面板 -->
<view class="express-panel">
<!-- 快递单号行 -->
<view class="info-row">
<view class="icon-wrap">
<u-icon name="bag" color="#333333" size="20"></u-icon>
</view>
<text class="info-label" v-if="displayExpressName">{{ displayExpressName }}:</text>
<text class="info-label" v-else>快递单号:</text>
<text class="info-val">{{ displayExpressNo || '--' }}</text>
<view class="copy-btn" v-if="displayExpressNo" @click="copyData(displayExpressNo)">
<up-image src="/static/common/copy.png" width="14" height="14" bgColor="#f1f6ff00"></up-image>
</view>
</view>
<!-- 收货地址行 -->
<view class="info-row align-start" style="margin-top: 24rpx;" v-if="displayAddress">
<view class="icon-wrap" style="margin-top: 4rpx;">
<u-icon name="map" color="#333333" size="20"></u-icon>
</view>
<text class="info-label">收货地址:</text>
<text class="info-val address-text">{{ displayAddress }}</text>
</view>
<!-- 分割线 -->
<view class="divider"></view>
<!-- 物流轨迹时间轴 -->
<view class="trace-list" v-if="express && express.logisticsTraceDetailList && express.logisticsTraceDetailList.length > 0">
<view
class="trace-item"
v-for="(item, index) in express.logisticsTraceDetailList"
:key="index"
>
<!-- 左侧时间轴 (点 + 连接线) -->
<view class="axis-col">
<view :class="['dot', { 'is-latest': index === 0 }]"></view>
<view class="line" v-if="index < express.logisticsTraceDetailList.length - 1"></view>
</view>
<!-- 右侧轨迹内容 -->
<view class="content-col">
<view class="trace-header">
<text :class="['trace-status', { 'is-latest': index === 0 }]">
{{ getTraceTitle(item, index) }}
</text>
<text class="trace-time">{{ item.curTime || item.time }}</text>
</view>
<view class="trace-desc" v-if="item.desc">
{{ item.desc }}
</view>
</view>
</view>
</view>
<!-- 空数据提示 -->
<view class="empty-box" v-else>
<text class="empty-text">暂无物流轨迹信息</text>
</view>
</view>
</view>
<up-toast ref="uToastRef"></up-toast>
<!-- 腾讯电子签H5 → 集成到UniApp -->
<qiaobao-assistant page-key="pages/rwa_package/express/express" title="物流详情" />
</view>
</template>
<script>
import Header from '@/components/common/header.vue';
import TopSafe from '@/components/common/top-safe.nvue';
import MyBtn from '@/components/common/my-btn.vue';
import func from '@/utils/func.js';
import { copyData } from '@/utils/index.js';
import { getDetail, orderLogistics } from "@/api/order.js";
export default {
components: { Header, TopSafe, MyBtn },
data() {
return {
express: null,
curData: null,
id: 0,
expressList: [],
expressId: '',
currentTab: 0,
};
},
computed: {
currentPackage() {
return this.expressList[this.currentTab] || {};
},
displayExpressName() {
return this.currentPackage.expressName || this.curData?.items?.[0]?.expressName || this.express?.expressName || '';
},
displayExpressNo() {
return this.currentPackage.expressNo || this.expressId || this.curData?.items?.[0]?.expressNo || '';
},
displayAddress() {
if (!this.curData) return '';
const parts = [
this.curData.countryName,
this.curData.cityName,
this.curData.areaName,
this.curData.address
].filter(Boolean);
return parts.join(' ');
}
},
onLoad(option) {
if (option) {
if (option.id) {
this.id = Number(option.id);
this.detail();
} else if (option.expressNo) {
this.expressId = option.expressNo;
this.expressList = [{ expressNo: option.expressNo }];
this.getOrderLogistics();
}
}
},
methods: {
copyData,
getTabName(index) {
const zhNums = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
return `物流${zhNums[index] || (index + 1)}`;
},
getTraceTitle(item, index) {
if (item.areaName) return item.areaName;
if (item.statusName) return item.statusName;
if (item.logisticsStatusDesc) return item.logisticsStatusDesc;
if (item.action) return item.action;
if (item.status) return item.status;
return index === 0 ? '已签收' : '运输中';
},
onSelectTab(index) {
this.currentTab = index;
const pkg = this.expressList[index];
if (pkg && pkg.expressNo) {
this.expressId = pkg.expressNo;
this.getOrderLogistics();
}
},
detail() {
let params = {
id: this.id
};
getDetail(params).then(res => {
if (res.bizcode == 100) {
this.curData = res.data;
this.expressList = this.curData?.orderPackages || [];
if (this.expressList.length > 0) {
this.onSelectTab(0);
} else if (this.curData?.items?.[0]?.expressNo) {
this.expressList = [{
expressName: this.curData.items[0].expressName,
expressNo: this.curData.items[0].expressNo
}];
this.onSelectTab(0);
}
}
});
},
getOrderLogistics() {
if (!this.expressId) return;
let params = {
mailNo: this.expressId
};
orderLogistics(params).then(res => {
if (res.bizcode == 100 && res.data) {
this.express = res.data;
if (Array.isArray(this.express.logisticsTraceDetailList)) {
this.express.logisticsTraceDetailList.forEach(item => {
if (item.time) {
try {
item.curTime = func.formatDate(new Date(item.time), 2);
} catch (e) {
item.curTime = item.time;
}
}
});
}
} else {
this.express = null;
}
});
}
}
}
</script>
<style lang="scss" scoped>
.content {
width: 100vw;
min-height: 100vh;
background-color: #ffffff;
display: flex;
flex-direction: column;
}
.main-body {
width: 100%;
flex: 1;
display: flex;
flex-direction: column;
}
.tabs-bar {
width: 100%;
padding: 24rpx 32rpx 16rpx 32rpx;
box-sizing: border-box;
background-color: #ffffff;
.tabs-scroll {
width: 100%;
white-space: nowrap;
.tabs-wrapper {
display: inline-flex;
flex-direction: row;
align-items: center;
}
.tab-item {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12rpx 36rpx;
border-radius: 32rpx;
font-size: 28rpx;
color: #333333;
background-color: #F5F5F5;
margin-right: 24rpx;
transition: all 0.2s ease;
&.active {
background-color: #F2E9FF;
color: #7934F6;
font-weight: 600;
}
}
}
}
.express-panel {
width: 100%;
flex: 1;
padding: 20rpx 32rpx 40rpx 32rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
.info-row {
display: flex;
flex-direction: row;
align-items: center;
&.align-start {
align-items: flex-start;
}
.icon-wrap {
display: flex;
align-items: center;
justify-content: center;
margin-right: 12rpx;
flex-shrink: 0;
}
.info-label {
font-size: 28rpx;
color: #333333;
margin-right: 12rpx;
flex-shrink: 0;
}
.info-val {
font-size: 28rpx;
color: #333333;
font-weight: 500;
&.address-text {
font-weight: normal;
line-height: 1.5;
word-break: break-all;
}
}
.copy-btn {
margin-left: 16rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
.divider {
width: 100%;
height: 1rpx;
background-color: #F5F5F5;
margin: 32rpx 0 40rpx 0;
}
.trace-list {
width: 100%;
display: flex;
flex-direction: column;
.trace-item {
display: flex;
flex-direction: row;
position: relative;
padding-bottom: 44rpx;
&:last-child {
padding-bottom: 0;
}
.axis-col {
width: 36rpx;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
flex-shrink: 0;
.dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
border: 3rpx solid #CCCCCC;
background-color: #FFFFFF;
z-index: 2;
margin-top: 8rpx;
box-sizing: border-box;
&.is-latest {
width: 20rpx;
height: 20rpx;
background-color: #7934F6;
border: none;
margin-top: 6rpx;
}
}
.line {
position: absolute;
top: 24rpx;
bottom: -10rpx;
left: 50%;
transform: translateX(-50%);
width: 2rpx;
background-color: #EEEEEE;
z-index: 1;
}
}
.content-col {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
.trace-header {
display: flex;
flex-direction: row;
align-items: center;
.trace-status {
font-size: 28rpx;
color: #333333;
font-weight: 500;
&.is-latest {
color: #7934F6;
font-weight: 600;
}
}
.trace-time {
font-size: 24rpx;
color: #999999;
margin-left: 20rpx;
}
}
.trace-desc {
font-size: 26rpx;
color: #666666;
line-height: 1.5;
margin-top: 12rpx;
word-break: break-all;
}
}
}
}
.empty-box {
width: 100%;
padding: 60rpx 0;
display: flex;
justify-content: center;
align-items: center;
.empty-text {
font-size: 28rpx;
color: #999999;
}
}
}
</style>