| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- <template>
- <el-drawer v-model="visible" title="物流信息" size="38%" direction="rtl" :before-close="handleClose" :close-on-click-modal="true">
- <div class="logistics-detail">
- <div class="section-title">单号查询</div>
- <el-form :model="form" label-width="80px">
- <el-form-item label="物流单号">
- <el-select v-model="form.selectedLogisticNo" placeholder="请选择物流单号" @change="handleLogisticNoChange" style="width: 100%">
- <el-option
- v-for="item in logisticsList"
- :key="item.id"
- :label="`${item.logisticNo || item.deliverCode},${getDictLabel(deliver_method, item.deliverMethod)}`"
- :value="item.logisticNo || item.deliverCode"
- />
- </el-select>
- </el-form-item>
- </el-form>
- <div class="section-title">物流信息</div>
- <el-timeline v-if="logisticsInfo.length > 0">
- <el-timeline-item v-for="(item, index) in logisticsInfo" :key="index" :timestamp="item.time" placement="top">
- <div class="timeline-content">
- <div class="timeline-status">{{ index + 1 }}</div>
- <div class="timeline-detail">
- <div>{{ item.time }}</div>
- <div>{{ item.location }}</div>
- <div>{{ item.status }}</div>
- <div v-if="item.imagesUrl" class="timeline-images">
- <div class="image-label">签收图片:</div>
- <div class="image-list">
- <el-image
- v-for="(url, imgIndex) in item.imagesUrl.split(',')"
- :key="imgIndex"
- :src="url"
- :preview-src-list="item.imagesUrl.split(',')"
- :initial-index="imgIndex"
- fit="cover"
- class="sign-image"
- >
- <template #error>
- <div class="image-error">
- <el-icon><Picture /></el-icon>
- </div>
- </template>
- </el-image>
- </div>
- </div>
- </div>
- </div>
- </el-timeline-item>
- </el-timeline>
- <el-empty v-else description="暂无物流信息" />
- </div>
- </el-drawer>
- </template>
- <script setup lang="ts">
- import { listOrderDeliver, queryTrack } from '@/api/order/orderDeliver';
- import { OrderDeliverVO } from '@/api/order/orderDeliver/types';
- import { listOrderStatusLog } from '@/api/order/orderStatusLog';
- import { Picture } from '@element-plus/icons-vue';
- interface Props {
- modelValue: boolean;
- orderId?: string | number;
- }
- interface LogisticsInfo {
- time: string;
- location: string;
- status: string;
- imagesUrl: string;
- }
- const props = defineProps<Props>();
- const emit = defineEmits(['update:modelValue']);
- const { proxy } = getCurrentInstance() as ComponentInternalInstance;
- const { deliver_method } = toRefs<any>(proxy?.useDict('deliver_method'));
- const visible = ref(false);
- const logisticsList = ref<OrderDeliverVO[]>([]);
- const form = ref({
- id: null,
- selectedLogisticNo: ''
- });
- const logisticsInfo = ref<LogisticsInfo[]>([]);
- watch(
- () => props.modelValue,
- (val) => {
- visible.value = val;
- if (val && props.orderId) {
- loadLogisticsList();
- }
- }
- );
- watch(visible, (val) => {
- emit('update:modelValue', val);
- });
- const loadLogisticsList = async () => {
- try {
- const res = await listOrderDeliver({
- orderId: props.orderId,
- pageNum: 1,
- pageSize: 100
- });
- logisticsList.value = res.rows || [];
- if (logisticsList.value.length > 0) {
- form.value.selectedLogisticNo = logisticsList.value[0].logisticNo || logisticsList.value[0].deliverCode;
- form.value.id = logisticsList.value[0].id;
- handleLogisticNoChange(form.value.selectedLogisticNo, form.value.id);
- }
- } catch (error) {
- console.error('Failed to load logistics list:', error);
- }
- };
- const handleLogisticNoChange = async (logisticNo: string, id: string) => {
- const selected = logisticsList.value.find((item) => item.logisticNo === logisticNo);
- try {
- if (selected) {
- const res = await queryTrack({
- id: form.value.id,
- logisticNo: logisticNo,
- logisticsCompanyCode: selected.logisticsCompanyCode,
- //先使用送货人手机号其实应该是收货人手机号
- phone: selected.consigneePhone,
- pageNum: 1,
- pageSize: 100
- });
- // 1. 兼容处理:有些接口返回在 res.data,有些可能直接是 res
- const dataList = res.data || [];
- if (Array.isArray(dataList) && dataList.length > 0) {
- logisticsInfo.value = dataList.map((item: any) => {
- // 2. 核心修复:精准匹配时间字段
- // 顺丰用 'time',韵达用 'ftime'。
- // 优先取 ftime (韵达标准),如果没有则取 time (顺丰标准)
- const displayTime = item.ftime || item.time || item.acceptTime || '';
- return {
- time: displayTime,
- // 3. 建议:保留原始状态字段,方便后续筛选(如"已签收")
- status: item.context || item.content || '',
- // 4. 建议:如果有地址字段,也可以映射进来,没有则保持订单号
- location: item.location || (selected.orderCode ? `${selected.orderCode}` : ''),
- imagesUrl: ''
- };
- });
- }
- } else {
- await listOrderStatusLog({
- orderId: props.orderId,
- logisticNos: form.value.selectedLogisticNo,
- logisticsCompanyCode: selected?.logisticsCompanyCode,
- pageNum: 1,
- pageSize: 100
- }).then((res) => {
- logisticsInfo.value = res.rows.map((item: any) => {
- return {
- time: item.createTime,
- location: item.orderCode ? `${item.orderCode}` : '',
- status: item.statusName,
- imagesUrl: item.imagesUrl
- };
- });
- });
- // logisticsInfo.value = [
- // {
- // time: (selected as any).createTime || '',
- // location: selected.orderCode ? `${selected.orderCode}` : '',
- // status: '已下单'
- // }
- // ];
- }
- } catch (error) {
- console.error('Failed to query track:', error);
- logisticsInfo.value = [
- {
- time: (selected as any).createTime || '',
- location: selected.orderCode ? `${selected.orderCode}` : '',
- status: '已下单',
- imagesUrl: ''
- }
- ];
- }
- };
- const getDictLabel = (dictOptions: any[], value: string) => {
- if (!dictOptions || !value) return value;
- const dict = dictOptions.find((item) => item.value === value);
- return dict ? dict.label : value;
- };
- const handleClose = () => {
- visible.value = false;
- form.value.selectedLogisticNo = '';
- logisticsInfo.value = [];
- };
- </script>
- <style scoped lang="scss">
- .logistics-detail {
- .section-title {
- font-size: 16px;
- font-weight: 500;
- margin-bottom: 16px;
- color: #303133;
- }
- .timeline-content {
- display: flex;
- align-items: flex-start;
- gap: 12px;
- .timeline-status {
- width: 24px;
- height: 24px;
- border-radius: 50%;
- background-color: #409eff;
- color: white;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 12px;
- flex-shrink: 0;
- }
- .timeline-detail {
- flex: 1;
- div {
- margin-bottom: 4px;
- color: #606266;
- font-size: 14px;
- &:last-child {
- margin-bottom: 0;
- }
- }
- .timeline-images {
- margin-top: 8px;
- padding-top: 8px;
- border-top: 1px dashed #dcdfe6;
- .image-label {
- font-size: 13px;
- color: #909399;
- margin-bottom: 6px;
- }
- .image-list {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- .sign-image {
- width: 60px;
- height: 60px;
- border-radius: 4px;
- border: 1px solid #dcdfe6;
- cursor: pointer;
- .image-error {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #f5f7fa;
- color: #c0c4cc;
- }
- }
- }
- }
- }
- }
- :deep(.el-timeline-item__timestamp) {
- display: none;
- }
- }
- </style>
|