张田田 hai 1 mes
pai
achega
e194612d31
Modificáronse 1 ficheiros con 227 adicións e 1 borrados
  1. 227 1
      src/service/SingleItemDetail.vue

+ 227 - 1
src/service/SingleItemDetail.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import type { SystemCwModel } from '@/model/care.model';
 import type { ApplyRecordModel } from '@/model/order.model';
-import { PlayCircleOutlined } from '@ant-design/icons-vue';
+import { DownOutlined, PlayCircleOutlined, UpOutlined } from '@ant-design/icons-vue';
 import { computed, h, onMounted, ref } from 'vue';
 import { notification } from 'ant-design-vue';
 import VxeUI from 'vxe-table';
@@ -55,6 +55,80 @@ const mockLogisticsData = computed(() => {
     recipientAddress: (props.data.provinceName !== null ? props.data.provinceName + ' ' : '') + (props.data.cityName !== null ? props.data.cityName + ' ' : '') + (props.data.areaName !== null ? props.data.areaName + ' ' : '') + (props.data.detailAddress !== null ? props.data.detailAddress : '')
   };
 });
+type LogisticsTimelineItem = {
+  title: string;
+  time: string;
+  detail: string;
+};
+
+const showAllLogistics = ref(false);
+const mockLogisticsTimeline: LogisticsTimelineItem[] = [
+  {
+    title: '已签收',
+    time: '01-09 19:15',
+    detail: '您已在杭州金成花园15幢4号后完成取件,感谢使用菜鸟驿站,期待再次为您服务。',
+  },
+  {
+    title: '待取件',
+    time: '01-09 16:11',
+    detail: '您的快件已暂存在杭州金成花园15幢4号店菜鸟驿站,请凭取货码及时领取。如有疑问请联系15067159925',
+  },
+  {
+    title: '运输中',
+    time: '01-09 13:26',
+    detail: '快件离开杭州分拨中心,正在发往目的地站点。',
+  },
+  {
+    title: '包裹已揽收',
+    time: '01-09 09:42',
+    detail: '快递员已揽收您的包裹,正在安排发出。',
+  },
+];
+
+function normalizeLogisticsItem(item: any): LogisticsTimelineItem | null {
+  if (!item || typeof item !== 'object') return null;
+  const time = String(item.time ?? item.acceptTime ?? item.ftime ?? item.createTime ?? item.updateTime ?? '').trim();
+  const title = String(item.title ?? item.status ?? item.statusName ?? item.statusDesc ?? '').trim();
+  const detail = String(item.content ?? item.context ?? item.remark ?? item.description ?? item.message ?? '').trim();
+  const fallbackTitle = detail || title;
+  if (!time && !fallbackTitle && !detail) return null;
+  return {
+    title: fallbackTitle || '-',
+    time,
+    detail: detail && detail !== fallbackTitle ? detail : '',
+  };
+}
+
+const logisticsTimeline = computed<LogisticsTimelineItem[]>(() => {
+  const source = props.data as any;
+  const listCandidate =
+    source?.expressDetails ??
+    source?.logisticsDetails ??
+    source?.logisticsList ??
+    source?.expressRouteList ??
+    source?.expressRoutes ??
+    source?.traces ??
+    source?.traceList ??
+    source?.routeList ??
+    source?.tracks ??
+    [];
+  if (!Array.isArray(listCandidate)) return mockLogisticsTimeline;
+  const normalizedList = listCandidate
+    .map((item: any) => normalizeLogisticsItem(item))
+    .filter((item): item is LogisticsTimelineItem => Boolean(item));
+  return normalizedList.length > 0 ? normalizedList : mockLogisticsTimeline;
+});
+
+const visibleLogisticsTimeline = computed(() => {
+  if (showAllLogistics.value) return logisticsTimeline.value;
+  return logisticsTimeline.value.slice(0, 2);
+});
+
+const canExpandLogistics = computed(() => logisticsTimeline.value.length > 2);
+
+function toggleLogisticsExpand() {
+  showAllLogistics.value = !showAllLogistics.value;
+}
 const expressTypeText: Record<string, string> = {
   '0': '邮政速递',
   '1': '顺丰速运',
@@ -283,10 +357,48 @@ onMounted(async () => {
         <!-- 物流信息 -->
         <div class="logistics-content" v-if="data.receiptType === '0'">
           <template v-if="mockPackageItems.length > 0">
+            <div class="flex align-items-center justify-between">
             <div class="logistics-tracking">
               <span class="tracking-number">{{ mockLogisticsData.trackingNumber }}</span>
               <a @click="handleCopyTracking" class="copy-link">复制</a>
             </div>
+            <div class="text-gray-500">联系电话:9554</div>
+          </div>
+            <!-- 物流详情 -->
+            <div class="logistics-detail">
+              <template v-if="logisticsTimeline.length > 0">
+                <div class="logistics-timeline">
+                  <div class="timeline-item" v-for="(item, index) in visibleLogisticsTimeline" :key="`${item.time}-${index}`">
+                    <div class="timeline-dot-wrap">
+                      <span class="timeline-dot" :class="{ active: index === 0 }" />
+                      <span class="timeline-line" v-if="index !== visibleLogisticsTimeline.length - 1 || canExpandLogistics" />
+                    </div>
+                    <div class="timeline-content">
+                      <div class="timeline-head">
+                        <span class="timeline-title" :class="{ active: index === 0 }">{{ item.title || '-' }}</span>
+                        <span class="timeline-time" :class="{ active: index === 0 }">{{ item.time || '-' }}</span>
+                      </div>
+                      <div class="timeline-detail" v-if="item.detail">{{ item.detail }}</div>
+                    </div>
+                  </div>
+                  <div class="timeline-item timeline-expand-item" v-if="canExpandLogistics">
+                    <div class="timeline-dot-wrap">
+                      <span class="timeline-dot" />
+                    </div>
+                    <div class="timeline-content timeline-expand-content">
+                      <a @click="toggleLogisticsExpand" class="expand-link">
+                        {{ showAllLogistics ? '收起物流明细' : '展开更多物流明细' }}
+                        <UpOutlined v-if="showAllLogistics" class="expand-arrow" />
+                        <DownOutlined v-else class="expand-arrow" />
+                      </a>
+                    </div>
+                  </div>
+                </div>
+              </template>
+              <template v-else>
+                <div class="logistics-empty">暂无物流轨迹</div>
+              </template>
+            </div>
             <div class="logistics-recipient">
               <span class="receive-icon">收</span>
               <span class="recipient-info">
@@ -805,6 +917,120 @@ onMounted(async () => {
     text-decoration: underline;
   }
 
+  .logistics-detail {
+    max-width: 760px;
+  }
+
+  .logistics-timeline {
+    margin-top: 2px;
+  }
+
+  .timeline-item {
+    display: flex;
+    align-items: flex-start;
+    gap: 10px;
+  }
+
+  .timeline-dot-wrap {
+    width: 14px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    flex-shrink: 0;
+    padding-top: 5px;
+  }
+
+  .timeline-dot {
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    background: #d9d9d9;
+  }
+
+  .timeline-dot.active {
+    background: #ff7a45;
+  }
+
+  .timeline-line {
+    width: 1px;
+    min-height: 44px;
+    background: #e8e8e8;
+    margin-top: 4px;
+  }
+
+  .timeline-content {
+    flex: 1;
+    min-width: 0;
+    padding-bottom: 12px;
+  }
+
+  .timeline-head {
+    display: flex;
+    align-items: baseline;
+    gap: 8px;
+    line-height: 22px;
+  }
+
+  .timeline-title {
+    color: #666;
+    font-size: 15px;
+    font-weight: 500;
+  }
+
+  .timeline-title.active {
+    color: #ff7a45;
+  }
+
+  .timeline-time {
+    color: #8c8c8c;
+    font-size: 13px;
+  }
+
+  .timeline-time.active {
+    color: #ff7a45;
+  }
+
+  .timeline-detail {
+    margin-top: 2px;
+    line-height: 22px;
+    color: #8c8c8c;
+    font-size: 14px;
+    word-break: break-all;
+  }
+
+  .timeline-expand-item {
+    margin-top: -2px;
+  }
+
+  .timeline-expand-content {
+    padding-bottom: 0;
+  }
+
+  .expand-link {
+    color: #666;
+    font-size: 14px;
+    text-decoration: none;
+    cursor: pointer;
+    user-select: none;
+    display: flex;
+    align-items: center;
+  }
+
+  .expand-link:hover {
+    color: #1890ff;
+  }
+
+  .expand-arrow {
+    margin-left: 4px;
+    font-size: 10px;
+    transform: translateY(1px);
+  }
+
+  .logistics-empty {
+    color: #999;
+    line-height: 22px;
+  }
+
   .logistics-recipient {
     display: flex;
     align-items: flex-start;