252 lines
7.5 KiB
Vue
Raw Normal View History

2026-07-27 11:26:39 +08:00
<template>
<full-page v-if="data">
<view :style="elderVarStyle">
<view class="px-15 py-12 bg-white shadow-lg leading-normal">
<view class="pb-5">
<text class="text-base text-gray">收货人</text>
<text class="text-base text-dark">{{ data.receiveName }} {{ data.receivePhone }}</text>
</view>
<view>
<text class="text-base text-gray">收货地址</text>
<text v-if="data.receiveRegion && data.receiveRegion.length > 0" class="text-base text-dark">
{{ data.receiveRegion.join('') }}
</text>
<text v-if="data.receiveAddress" class="text-base text-dark">
{{ data.receiveAddress }}
</text>
</view>
</view>
<view class="mt-15 bg-white shadow-lg">
<view class="flex items-center px-15 py-12 border-b">
<view v-if="data.shippingType === 'fengniao'" class="flex-shrink-0 flex items-center">
<image class="flex-shrink-0 shipping-icon mr-5" src="/static/icons/fengniao.svg"></image>
<view class="flex-shrink-0 mr-10 text-base text-drak">蜂鸟配送</view>
</view>
<view v-else-if="data.shippingType === 'ems'" class="flex-shrink-0 flex items-center">
<image class="flex-shrink-0 shipping-icon mr-5" src="/static/icons/ems.svg"></image>
<view class="flex-shrink-0 mr-10 text-base text-drak">快递邮寄</view>
</view>
<view class="flex-grow w-0 flex items-center justify-end">
<view class="flex-grow w-0 truncate text-sm text-dark text-right mr-5">
{{ data.shippingNo }}
</view>
<view class="flex-shrink-0 text-base text-primary" @click="copy">复制</view>
</view>
</view>
<view class="px-15 pb-50">
<view v-for="(i, idx) in eventList" class="route-item pt-5 relative"
:class="{ active: idx === 0, last: idx === eventList.length - 1 }">
<view class="route-header flex items-center flex-wrap">
<view class="mr-5 text-base font-semibold" :class="idx === 0 ? 'text-warning' : 'text-dark'">
{{ i.status }}
</view>
<view class="text-sm" :class="idx === 0 ? 'text-warning' : 'text-gray'">{{ i.time }}</view>
</view>
<template v-if="data.shippingType === 'fengniao'">
<view v-if="i.knightName || i.knightPhone" class="flex flex-wrap pb-10 leading-normal">
<view v-if="i.knightName" class="flex-shrink-0">
<text class="text-sm text-gray">骑手</text>
<text class="mr-10 text-sm text-primary">{{ i.knightName }}</text>
</view>
<view v-if="i.knightPhone" class="flex-shrink-0">
<text class="text-sm text-gray">骑手电话</text>
<text class="text-sm text-primary" @click="contact(i.knightPhone)">{{ i.knightPhone }}</text>
</view>
</view>
</template>
<template v-else-if="data.shippingType === 'ems'">
<view class="text-base text-dark leading-normal">{{ i.place }}</view>
<view class="leading-normal">
<text v-for="item in i.descTextList" :key="item.key" class="mr-10 text-sm"
:class="item.type === 'phone' ? 'text-primary' : 'text-dark'"
@click="contact(item.type === 'phone' ? item.text : '')">
{{ item.text }}
</text>
</view>
</template>
</view>
<empty-data v-if="eventList.length === 0" height="50vh" />
</view>
</view>
</view>
</full-page>
</template>
<script setup>
import { computed, ref } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import useElder from '@/hooks/useElder';
import useUser from '@/hooks/useUser';
import { getShippingDetail } from '@/utils/api';
import { toast, loading as showLoading, hideLoading } from '@/utils/widget';
import fullPage from '@/components/full-page.vue';
import emptyData from "@/components/empty.vue";
const { elderVarStyle } = useElder();
const id = ref('');
const data = ref(null)
const { userInfo } = useUser();
const eventList = computed(() => {
if (data.value && data.value.shippingType === 'fengniao') {
const shippingDetail = data.value.shippingDetail || {};
const eventLogDetails = Array.isArray(shippingDetail.eventLogDetails) ? shippingDetail.eventLogDetails : [];
return eventLogDetails.map(item => ({
knightName: item.knightName,
knightPhone: item.knightPhone,
time: item.time,
status: item.status
})).sort((a, b) => new Date(b.time) - new Date(a.time));
} else if (data.value && data.value.shippingType === 'ems') {
const eventLogDetails = Array.isArray(data.value.shippingDetail) ? data.value.shippingDetail : [];
return eventLogDetails.map((i, idx) => ({
status: i.opName,
time: i.opTime,
place: `${i.opOrgProvName}-${i.opOrgCity}-${i.opOrgName}`,
descTextList: getDescTextList(i.opDesc, idx),
}))
}
return [];
})
function copy() {
uni.setClipboardData({
data: data.value.shippingNo,
success: () => toast('复制成功')
})
}
function contact(phoneNumber) {
if (!phoneNumber) return;
uni.makePhoneCall({
phoneNumber,
})
}
function getDescTextList(desc, index) {
if (typeof desc !== 'string') return [];
// 匹配11位手机号以1开头的数字
const phoneRegex = /1[3-9]\d{9}/g;
const result = [];
let lastIndex = 0;
let match;
// 查找所有手机号
while ((match = phoneRegex.exec(desc)) !== null) {
// 添加手机号之前的普通文本
if (match.index > lastIndex) {
const textBefore = desc.substring(lastIndex, match.index);
if (textBefore) {
result.push({
text: textBefore,
type: 'text'
});
}
}
// 添加手机号
result.push({
text: match[0],
type: 'phone'
});
lastIndex = match.index + match[0].length;
}
// 添加最后一个手机号之后的普通文本
if (lastIndex < desc.length) {
const textAfter = desc.substring(lastIndex);
if (textAfter) {
result.push({
text: textAfter,
type: 'text'
});
}
}
// 如果没有找到手机号,返回整个文本作为普通文本
if (result.length === 0) {
result.push({
text: desc,
type: 'text'
});
}
return result.map((i, idx) => ({
key: `${index}-${Date.now()}-${idx}`,
text: i.text,
type: i.type
}));
}
async function getDetail() {
showLoading();
const res = await getShippingDetail({ accountId: userInfo.value.userId, id: id.value });
hideLoading();
if (res && res.success) {
data.value = res.data;
} else {
await toast(res.message || '获取配送信息失败');
uni.navigateBack();
}
}
onLoad(options => {
id.value = options.id || '';
})
onShow(() => {
getDetail()
})
</script>
<style scoped>
page {
background: #f5f5f5;
}
.shipping-icon {
width: 48rpx;
height: 48rpx;
}
.route-item {
padding-left: 60rpx
}
.route-item::before {
content: "";
position: absolute;
top: 33rpx;
left: 22rpx;
width: 16rpx;
height: 16rpx;
background: #999;
border-radius: 50%;
}
.route-item.active::before {
background: #f56c6c;
}
.route-item::after {
content: "";
position: absolute;
top: 53rpx;
bottom: -24rpx;
left: 28rpx;
border-left: 4rpx dashed #999;
}
.route-item.last::after {
display: none;
}
.route-header {
min-height: 72rpx;
}
.pb-50 {
padding-bottom: 100rpx;
}
</style>