246 lines
7.3 KiB
Vue
246 lines
7.3 KiB
Vue
|
<template>
|
|||
|
<el-drawer v-model="drawer" :direction="direction" size="40%" :before-close="handleBeforeClose" title-class="drawer-title">
|
|||
|
<template #header>
|
|||
|
<span class="font-bold text-lg text-gray-800">物流信息</span>
|
|||
|
</template>
|
|||
|
|
|||
|
<template #default>
|
|||
|
<!-- 物流头部信息 -->
|
|||
|
<div class="bg-white rounded-lg shadow-md p-5 mb-6">
|
|||
|
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
|||
|
<!-- 左侧:快递基本信息 -->
|
|||
|
<div class="flex items-center gap-6">
|
|||
|
<div class="w-14 h-14 rounded-md overflow-hidden border border-gray-100 flex items-center justify-center">
|
|||
|
<img
|
|||
|
:src="logisticsData?.result.logo"
|
|||
|
alt="快递公司Logo"
|
|||
|
class="w-full h-full object-contain"
|
|||
|
:onerror="`this.src='https://via.placeholder.com/48x48?text=暂无Logo'`"
|
|||
|
/>
|
|||
|
</div>
|
|||
|
<div class="text-sm">
|
|||
|
<p class="text-gray-500">快递单号</p>
|
|||
|
<p class="font-medium text-gray-900">{{ logisticsData?.result.number }}</p>
|
|||
|
<p class="text-gray-500 mt-1">{{ logisticsData?.result.expName }} | 最新更新: {{ logisticsData?.result.updateTime }}</p>
|
|||
|
</div>
|
|||
|
<div class="ml-auto">
|
|||
|
<el-tag :type="getStatusType(logisticsData?.result.deliverystatus)" size="medium" class="px-4 py-1">
|
|||
|
{{ getStatusText(logisticsData?.result.deliverystatus) }}
|
|||
|
</el-tag>
|
|||
|
<p class="text-gray-500 text-sm mt-2 text-right">耗时: {{ logisticsData?.result.takeTime || '暂无数据' }}</p>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<!-- 快递员信息(有数据才显示) -->
|
|||
|
<div v-if="logisticsData?.result.courier" class="bg-blue-50 rounded-lg p-4 mb-6 border-l-4 border-blue-400">
|
|||
|
<div class="flex items-center justify-between">
|
|||
|
<p class="font-medium text-blue-800">配送信息</p>
|
|||
|
<a :href="`tel:${logisticsData?.result.courierPhone}`" class="text-blue-600 hover:text-blue-800 text-sm flex items-center gap-1">
|
|||
|
<el-icon class="el-icon-phone"></el-icon>
|
|||
|
联系快递员
|
|||
|
</a>
|
|||
|
</div>
|
|||
|
<div class="flex flex-wrap gap-x-8 gap-y-3 mt-3 text-gray-700">
|
|||
|
<div class="flex items-center gap-2">
|
|||
|
<el-icon class="el-icon-user text-gray-500"></el-icon>
|
|||
|
<span>快递员: {{ logisticsData?.result.courier }}</span>
|
|||
|
</div>
|
|||
|
<div class="flex items-center gap-2">
|
|||
|
<el-icon class="el-icon-phone-outline text-gray-500"></el-icon>
|
|||
|
<span>电话: {{ logisticsData?.result.courierPhone || '暂无' }}</span>
|
|||
|
</div>
|
|||
|
<div class="flex items-center gap-2">
|
|||
|
<el-icon class="el-icon-service text-gray-500"></el-icon>
|
|||
|
<span>客服: {{ logisticsData?.result.expPhone }}</span>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<!-- 物流轨迹列表 -->
|
|||
|
<div class="bg-white rounded-lg shadow-md p-5">
|
|||
|
<p class="font-medium text-gray-800 mb-4">物流轨迹({{ logisticsData?.result.list.length || 0 }}条)</p>
|
|||
|
<div class="relative" style="border-left: 1px solid #d9d9d9; padding-left: 15px">
|
|||
|
<div v-for="(item, index) in logisticsData?.result.list" :key="index" class="flex mb-8 relative">
|
|||
|
<div class="flex flex-col items-center mr-6 z-10">
|
|||
|
<div
|
|||
|
:class="[
|
|||
|
'w-8 h-8 rounded-full flex items-center justify-center',
|
|||
|
index === 0 ? 'bg-blue-500 text-white' : 'bg-white border border-gray-300 text-gray-500'
|
|||
|
]"
|
|||
|
>
|
|||
|
<el-icon v-if="index === 0" class="el-icon-check text-xs"></el-icon>
|
|||
|
<span v-else class="text-xs">{{ index + 1 }}</span>
|
|||
|
</div>
|
|||
|
<p class="text-xs text-gray-500 mt-2">{{ item.time }}</p>
|
|||
|
</div>
|
|||
|
<div class="flex-1 bg-gray-50 rounded-lg p-4 border border-gray-100 shadow-sm">
|
|||
|
<p class="text-gray-800">{{ item.status }}</p>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</template>
|
|||
|
|
|||
|
<template #footer>
|
|||
|
<div class="drawer-footer">
|
|||
|
<el-button @click="close" :loading="cancelLoading" class="mr-3">关闭</el-button>
|
|||
|
</div>
|
|||
|
</template>
|
|||
|
</el-drawer>
|
|||
|
</template>
|
|||
|
|
|||
|
<script setup lang="ts">
|
|||
|
import { ref } from 'vue';
|
|||
|
import type { DrawerProps } from 'element-plus';
|
|||
|
import { Phone, PhoneOutline, User, Service, Check } from '@element-plus/icons-vue';
|
|||
|
|
|||
|
// 抽屉方向
|
|||
|
const direction = ref<DrawerProps['direction']>('ltr');
|
|||
|
// 加载状态
|
|||
|
const cancelLoading = ref(false);
|
|||
|
const confirmLoading = ref(false);
|
|||
|
// 抽屉显隐
|
|||
|
const drawer = ref(false);
|
|||
|
// 物流数据(初始化为接口返回格式)
|
|||
|
const logisticsData = ref({
|
|||
|
status: '0',
|
|||
|
msg: 'ok',
|
|||
|
result: {
|
|||
|
number: '',
|
|||
|
type: '',
|
|||
|
list: [],
|
|||
|
deliverystatus: '0',
|
|||
|
issign: '0',
|
|||
|
expName: '',
|
|||
|
expSite: '',
|
|||
|
expPhone: '',
|
|||
|
courier: '',
|
|||
|
courierPhone: '',
|
|||
|
updateTime: '',
|
|||
|
takeTime: '',
|
|||
|
logo: ''
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
/**
|
|||
|
* 根据物流状态获取标签类型
|
|||
|
* @param status 物流状态码
|
|||
|
*/
|
|||
|
const getStatusType = (status?: string) => {
|
|||
|
switch (status) {
|
|||
|
case '0': // 揽件
|
|||
|
return 'info';
|
|||
|
case '1': // 在途中
|
|||
|
return 'warning';
|
|||
|
case '2': // 派件中
|
|||
|
return 'primary';
|
|||
|
case '3': // 已签收
|
|||
|
return 'success';
|
|||
|
case '4': // 派送失败
|
|||
|
case '5': // 疑难件
|
|||
|
return 'danger';
|
|||
|
case '6': // 退件签收
|
|||
|
return 'error';
|
|||
|
default:
|
|||
|
return 'default';
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* 根据物流状态获取文本描述
|
|||
|
* @param status 物流状态码
|
|||
|
*/
|
|||
|
const getStatusText = (status?: string) => {
|
|||
|
const statusMap: Record<string, string> = {
|
|||
|
'0': '快递收件(揽件)',
|
|||
|
'1': '运输途中',
|
|||
|
'2': '正在派件',
|
|||
|
'3': '已签收',
|
|||
|
'4': '派送失败',
|
|||
|
'5': '疑难件',
|
|||
|
'6': '退件签收'
|
|||
|
};
|
|||
|
return statusMap[status || '0'] || '未知状态';
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* 打开抽屉并加载物流数据
|
|||
|
*/
|
|||
|
const open = (data) => {
|
|||
|
const mockData = {
|
|||
|
result: data
|
|||
|
};
|
|||
|
logisticsData.value = mockData;
|
|||
|
drawer.value = true;
|
|||
|
};
|
|||
|
/**
|
|||
|
* 关闭抽屉
|
|||
|
*/
|
|||
|
const close = () => {
|
|||
|
drawer.value = false;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* 抽屉关闭前钩子(可用于拦截关闭逻辑)
|
|||
|
*/
|
|||
|
const handleBeforeClose = (done: () => void) => {
|
|||
|
done(); // 直接关闭,如需确认可添加弹窗逻辑
|
|||
|
};
|
|||
|
|
|||
|
// 暴露加载状态控制方法
|
|||
|
const setCancelLoading = (loading: boolean) => {
|
|||
|
cancelLoading.value = loading;
|
|||
|
};
|
|||
|
const setConfirmLoading = (loading: boolean) => {
|
|||
|
confirmLoading.value = loading;
|
|||
|
};
|
|||
|
|
|||
|
// 暴露方法供父组件调用
|
|||
|
defineExpose({
|
|||
|
open,
|
|||
|
setCancelLoading,
|
|||
|
setConfirmLoading
|
|||
|
});
|
|||
|
</script>
|
|||
|
|
|||
|
<style scoped>
|
|||
|
.drawer-title {
|
|||
|
padding-bottom: 8px;
|
|||
|
border-bottom: 1px solid #f2f2f2;
|
|||
|
}
|
|||
|
|
|||
|
.drawer-footer {
|
|||
|
display: flex;
|
|||
|
justify-content: flex-end;
|
|||
|
gap: 12px;
|
|||
|
padding: 16px;
|
|||
|
border-top: 1px solid #eee;
|
|||
|
}
|
|||
|
|
|||
|
:deep(.el-drawer__body) {
|
|||
|
padding: 20px;
|
|||
|
overflow-y: auto;
|
|||
|
max-height: calc(100vh - 160px);
|
|||
|
}
|
|||
|
|
|||
|
:deep(.el-tag) {
|
|||
|
border-radius: 4px;
|
|||
|
}
|
|||
|
|
|||
|
@media (max-width: 768px) {
|
|||
|
:deep(.el-drawer) {
|
|||
|
width: 95% !important;
|
|||
|
}
|
|||
|
|
|||
|
.drawer-footer {
|
|||
|
flex-direction: column;
|
|||
|
}
|
|||
|
|
|||
|
:deep(.drawer-footer .el-button) {
|
|||
|
width: 100%;
|
|||
|
}
|
|||
|
}
|
|||
|
</style>
|