2026-07-27 11:26:39 +08:00

252 lines
7.5 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>
<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>