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>
|