张田田 2 bulan lalu
induk
melakukan
4031efa6d7
4 mengubah file dengan 140 tambahan dan 190 penghapusan
  1. 105 163
      src/order/LogisticsDetails.vue
  2. 27 13
      src/order/Negotiations.vue
  3. 6 4
      src/order/Review.vue
  4. 2 10
      src/service/SingleItemDetail.vue

+ 105 - 163
src/order/LogisticsDetails.vue

@@ -1,7 +1,8 @@
 <script setup lang="ts">
-import { computed, ref } from 'vue';
+import { computed, onMounted, ref } from 'vue';
 import { notification } from 'ant-design-vue';
 import { DownOutlined, UpOutlined, StarOutlined } from '@ant-design/icons-vue';
+import { getLogisticsMethod } from '@/request/api/order.api';
 
 const props = defineProps<{
   data?: any;
@@ -20,54 +21,25 @@ const expressTypeText: Record<string, string> = {
   '7': '极兔速递',
 };
 
-// 假数据定义
-const mockData = {
-  receiptType: '0', // 0-快递 1-线下取货
-  expressType: '4', // 圆通
-  expressNo: 'YT7596102092888',
-  liaison: '何美丹',
-  phone: '158****5026',
-  provinceName: '浙江省',
-  cityName: '杭州市',
-  areaName: '西湖区',
-  detailAddress: '文新街道 文新街道西湖区金成花园13幢一单元602',
-  sameExpress: [
-    {
-      conditioningProgramPhoto: 'https://img0.baidu.com/it/u=3025010615,2026135017&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
-      conditioningProgramName: '元气茶',
-      convertDose: 10,
-      convertUnit: '包',
-      unitPrice: 32,
-      totalMeasure: 3,
-    },
-    {
-      conditioningProgramPhoto: 'https://img1.baidu.com/it/u=1067201099,2862800611&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
-      conditioningProgramName: '芡实米仁燕麦粥',
-      convertDose: 1,
-      convertUnit: '袋',
-      unitPrice: 1.5,
-      totalMeasure: 21,
-    }
-  ]
-};
+const displayData = computed(() => props.data || {});
 
-// 当前显示的数据
-const displayData = computed(() => {
-  return {
-    ...mockData,
-    // ...(props.data || {})
-  };
-});
+const logisticsTracks = ref<any[]>([]);
+
+// 获取物流信息
+async function getLogistics(id: string) {
+  const res = await getLogisticsMethod(id);
+  if (!res) return;
+  logisticsTracks.value = res?.tracks ?? [];
+}
 
 // 物流信息
 const logisticsInfo = computed(() => {
   const d = displayData.value;
   return {
-    expressName: expressTypeText[d.expressType || ''] || '圆通速递',
-    trackingNumber: d.expressNo || '无单号',
-    recipientName: d.liaison || '',
-    recipientPhone: d.phone || '',
-    recipientAddress: (d.provinceName || '') + ' ' + (d.cityName || '') + ' ' + (d.areaName || '') + ' ' + (d.detailAddress || '')
+    trackingNumber: `${expressTypeText[d.expressType ?? ''] ?? ''} ${d.expressNo ?? ''}`.trim(),
+    recipientName: d.liaison ? d.liaison + ', ' : '',
+    recipientPhone: d.phone ? d.phone + ', ' : '',
+    recipientAddress: [d.provinceName, d.cityName, d.areaName, d.detailAddress].filter((v) => v != null).join(' '),
   };
 });
 
@@ -77,44 +49,28 @@ type LogisticsTimelineItem = {
   detail: string;
 };
 
-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-08 13:26',
-    detail: '快件离开杭州分拨中心,正在发往目的地站点。',
-  },
-  {
-    title: '包裹已揽收',
-    time: '01-08 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;
+  const fallbackTitle = title || detail;
   if (!time && !fallbackTitle && !detail) return null;
   return {
-    title: fallbackTitle || '-',
+    title: title || '-',
     time,
-    detail: detail && detail !== fallbackTitle ? detail : '',
+    detail,
   };
 }
 
+function normalizeList(list: any[]): LogisticsTimelineItem[] {
+  if (!Array.isArray(list)) return [];
+  return list.map((item: any) => normalizeLogisticsItem(item)).filter((item): item is LogisticsTimelineItem => Boolean(item));
+}
+
 const timelineData = computed<LogisticsTimelineItem[]>(() => {
+  const fromApi = normalizeList(logisticsTracks.value);
+  if (fromApi.length > 0) return fromApi;
   const source = displayData.value;
   const listCandidate =
     source?.expressDetails ??
@@ -127,17 +83,10 @@ const timelineData = computed<LogisticsTimelineItem[]>(() => {
     source?.routeList ??
     source?.tracks ??
     [];
-  if (!Array.isArray(listCandidate) || listCandidate.length === 0) return mockLogisticsTimeline;
-  const normalizedList = listCandidate
-    .map((item: any) => normalizeLogisticsItem(item))
-    .filter((item): item is LogisticsTimelineItem => Boolean(item));
-  return normalizedList.length > 0 ? normalizedList : mockLogisticsTimeline;
+  return normalizeList(listCandidate);
 });
 
-const visibleTimeline = computed(() => {
-  if (showAllLogistics.value) return timelineData.value;
-  return timelineData.value.slice(0, 2);
-});
+const visibleTimeline = computed(() => (showAllLogistics.value ? timelineData.value : timelineData.value.slice(0, 2)));
 
 const canExpandLogistics = computed(() => timelineData.value.length > 2);
 
@@ -145,21 +94,31 @@ function toggleLogisticsExpand() {
   showAllLogistics.value = !showAllLogistics.value;
 }
 
-// 复制物流信息
 function handleCopyTracking() {
-  const trackingNumber = logisticsInfo.value.trackingNumber;
-  if (trackingNumber) {
-    navigator.clipboard.writeText(trackingNumber).then(() => {
+  const text = logisticsInfo.value.trackingNumber;
+  if (!text) return;
+  navigator.clipboard
+    .writeText(text)
+    .then(() => {
       notification.success({ message: '复制成功' });
-    }).catch(() => {
+    })
+    .catch(() => {
       notification.error({ message: '复制失败' });
     });
-  }
 }
 
-// 包裹商品
 const packageItems = computed(() => {
-  return displayData.value.sameExpress ?? []
+  const real = displayData.value.sameExpress;
+  if (real && real.length > 0) return real;
+  return [
+    { conditioningProgramName: '元气茶', convertDose: '30', convertUnit: 'g', unitPrice: '32.00', totalMeasure: 3, conditioningProgramPhoto: '' },
+    { conditioningProgramName: '芡实米仁燕麦粥', convertDose: '500', convertUnit: 'ml', unitPrice: '45.00', totalMeasure: 1, conditioningProgramPhoto: '' },
+    { conditioningProgramName: '枸杞养生茶', convertDose: '15', convertUnit: 'g', unitPrice: '28.00', totalMeasure: 2, conditioningProgramPhoto: '' },
+  ];
+});
+
+onMounted(async () => {
+  await getLogistics('1203');
 });
 </script>
 
@@ -167,20 +126,16 @@ const packageItems = computed(() => {
   <div class="logistics-details-container">
     <div v-if="displayData.receiptType === '0'">
       <!-- 通道/单号 -->
-      <div class="header-line">
+      <div class="header-line" v-if="logisticsInfo.trackingNumber">
         <div class="header-left">
-          <div class="express-logo">YT</div>
-          <span class="express-name">{{ logisticsInfo.expressName }}</span>
           <span class="tracking-no">{{ logisticsInfo.trackingNumber }}</span>
           <a @click="handleCopyTracking" class="copy-link">复制</a>
         </div>
-        <div class="header-right">
-          联系电话:95554
-        </div>
+        <!-- <div class="header-right">联系电话:9554</div> -->
       </div>
 
       <!-- 物流线 -->
-      <div class="timeline-box">
+      <div class="timeline-box" v-if="timelineData.length > 0">
         <div class="timeline-row" v-for="(item, index) in visibleTimeline" :key="index">
           <div class="dot-col">
             <span class="dot" :class="{ 'dot-active': index === 0 }"></span>
@@ -191,11 +146,10 @@ const packageItems = computed(() => {
               <span class="title-text">{{ item.title }}</span>
               <span class="time-text">{{ item.time }}</span>
             </div>
-            <div class="status-detail">{{ item.detail }}</div>
+            <div class="status-detail" v-if="item.detail">{{ item.detail }}</div>
           </div>
         </div>
 
-        <!-- 展开按钮 -->
         <div class="expand-row" v-if="canExpandLogistics">
           <div class="dot-col">
             <span class="dot"></span>
@@ -203,18 +157,18 @@ const packageItems = computed(() => {
           <div class="content-col">
             <a @click="toggleLogisticsExpand" class="expand-link">
               {{ showAllLogistics ? '收起更多物流明细' : '展开更多物流明细' }}
-              <DownOutlined :class="{ 'rotate-180': showAllLogistics }" class="arrow-icon" />
+              <UpOutlined v-if="showAllLogistics" class="arrow-icon" />
+              <DownOutlined v-else class="arrow-icon" />
             </a>
           </div>
         </div>
       </div>
+      <div v-else class="empty-box">暂无物流轨迹</div>
 
       <!-- 收货人行 -->
-      <div class="recipient-line">
+      <div class="recipient-line" v-if="logisticsInfo.recipientName || logisticsInfo.recipientPhone || logisticsInfo.recipientAddress">
         <span class="receive-badge">收</span>
-        <span class="recipient-text">
-          {{ logisticsInfo.recipientName }}, {{ logisticsInfo.recipientPhone }}, {{ logisticsInfo.recipientAddress }}
-        </span>
+        <span class="recipient-text"> {{ logisticsInfo.recipientName }} {{ logisticsInfo.recipientPhone }} {{ logisticsInfo.recipientAddress }} </span>
       </div>
 
       <!-- 包裹商品 -->
@@ -231,7 +185,7 @@ const packageItems = computed(() => {
             </div>
             <div class="item-main">
               <div class="item-name">{{ item.conditioningProgramName }}</div>
-              <div class="item-spec">{{ item.convertDose }}{{ item.convertUnit }}</div>
+              <div class="item-spec">{{ item.convertDose }} {{ item.convertUnit }}</div>
             </div>
             <div class="item-side">
               <div class="item-price">¥{{ item.unitPrice }}</div>
@@ -244,7 +198,7 @@ const packageItems = computed(() => {
 
     <!-- 线下 -->
     <div v-else-if="displayData.receiptType === '1'">
-      <div class="offline-box">线下取货模式</div>
+      <div class="offline-box">线下取货</div>
     </div>
 
     <div v-else class="empty-box">暂无物流数据</div>
@@ -253,52 +207,37 @@ const packageItems = computed(() => {
 
 <style scoped lang="scss">
 .logistics-details-container {
-  padding: 10px;
+  padding: 8px 10px;
   background: #fff;
   color: #333;
-  font-size: 14px;
+  font-size: 12px;
 }
 
-/* Header Line */
 .header-line {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 20px;
+  margin-bottom: 12px;
 
   .header-left {
     display: flex;
     align-items: center;
     gap: 12px;
 
-    .express-logo {
-      width: 24px;
-      height: 24px;
-      background: #722ed1;
-      color: #fff;
-      border-radius: 50%;
-      text-align: center;
-      line-height: 24px;
-      font-size: 10px;
-      font-weight: bold;
-    }
-
-    .express-name {
-      font-weight: bold;
-    }
-
     .tracking-no {
-      margin-left: 4px;
+      font-weight: 600;
+      color: #333;
     }
 
     .copy-link {
-      color: #999;
+      color: #666;
       font-size: 13px;
       cursor: pointer;
       text-decoration: none;
 
       &:hover {
-        color: #666;
+        color: #333;
+        text-decoration: underline;
       }
     }
   }
@@ -309,9 +248,8 @@ const packageItems = computed(() => {
   }
 }
 
-/* Timeline */
 .timeline-box {
-  margin-bottom: 24px;
+  margin-bottom: 16px;
 }
 
 .timeline-row {
@@ -326,8 +264,8 @@ const packageItems = computed(() => {
   width: 16px;
 
   .dot {
-    width: 10px;
-    height: 10px;
+    width: 8px;
+    height: 8px;
     background: #e8e8e8;
     border-radius: 50%;
     margin-top: 6px;
@@ -335,13 +273,13 @@ const packageItems = computed(() => {
   }
 
   .dot-active {
-    background: #FF6A00;
+    background: #ff6a00;
     box-shadow: 0 0 0 4px rgba(255, 106, 0, 0.1);
   }
 
   .line {
     width: 1px;
-    background: #f0f0f0;
+    background: #e8e8e8;
     flex: 1;
     min-height: 40px;
   }
@@ -349,29 +287,35 @@ const packageItems = computed(() => {
 
 .content-col {
   flex: 1;
-  padding-bottom: 24px;
+  padding-bottom: 14px;
 
   .status-title {
-    margin-bottom: 8px;
-    font-size: 15px;
+    margin-bottom: 4px;
+    font-size: 13px;
     display: flex;
-    gap: 10px;
+    gap: 8px;
     color: #666;
+    align-items: center;
 
     &.text-active {
-      color: #FF6A00;
+      color: #ff6a00;
       font-weight: 500;
     }
 
     .time-text {
-      font-size: 14px;
+      font-size: 12px;
+    }
+    .title-text {
+      font-size: 15px;
+      font-weight: 500;
+      align-items: center;
     }
   }
 
   .status-detail {
-    font-size: 14px;
+    font-size: 13px;
     color: #999;
-    line-height: 1.6;
+    line-height: 1.5;
   }
 }
 
@@ -381,68 +325,64 @@ const packageItems = computed(() => {
   margin-top: -10px;
 
   .expand-link {
-    color: #666;
-    font-size: 14px;
+    color: #009966;
+    font-size: 12px;
     cursor: pointer;
     display: flex;
     align-items: center;
     gap: 6px;
 
     &:hover {
-      color: #333;
+      color: #006633;
     }
 
     .arrow-icon {
       font-size: 10px;
-      transition: transform 0.3s;
-    }
-    .rotate-180 {
-      transform: rotate(180deg);
     }
   }
 }
 
-/* Recipient */
 .recipient-line {
   display: flex;
-  align-items: center;
+  align-items: flex-start;
   gap: 12px;
   margin-bottom: 24px;
 
   .receive-badge {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
     width: 20px;
     height: 20px;
-    background: #fff1f0;
-    border: 1px solid #ff4d4f;
-    color: #ff4d4f;
+    background: #ff6a00;
+    color: #fff;
     border-radius: 50%;
-    display: flex;
-    align-items: center;
-    justify-content: center;
     font-size: 12px;
+    font-weight: 500;
     flex-shrink: 0;
   }
 
   .recipient-text {
-    font-weight: 500;
     color: #333;
+    font-weight: 500;
+    font-size: 14px;
+    flex: 1;
   }
 }
 
-/* Package Items */
 .package-items-wrapper {
   border: 1px solid #e8e8e8;
   border-radius: 2px;
-  padding: 16px;
+  padding: 10px;
 
   .package-header {
     display: flex;
     align-items: center;
     gap: 8px;
-    margin-bottom: 16px;
+    // margin-bottom: 16px;
 
     .star-icon {
-      color: #ccc;
+      color: #ff6a00;
       font-size: 18px;
     }
 
@@ -458,7 +398,7 @@ const packageItems = computed(() => {
   align-items: center;
   gap: 16px;
   padding: 12px 0;
-  
+
   &:not(:last-child) {
     border-bottom: 1px solid #f5f5f5;
   }
@@ -476,7 +416,7 @@ const packageItems = computed(() => {
       height: 100%;
       object-fit: cover;
     }
-    
+
     .img-placeholder {
       display: flex;
       align-items: center;
@@ -487,7 +427,8 @@ const packageItems = computed(() => {
   }
 
   .item-main {
-    flex: 1;
+    // flex: 1;
+    width: 20%;
 
     .item-name {
       font-weight: 500;
@@ -516,7 +457,8 @@ const packageItems = computed(() => {
   }
 }
 
-.offline-box, .empty-box {
+.offline-box,
+.empty-box {
   padding: 40px;
   text-align: center;
   color: #999;

+ 27 - 13
src/order/Negotiations.vue

@@ -44,16 +44,16 @@ const mockNegotiationHistory: NegotiationItem[] = [
       { label: '退款类型', value: '退款' },
       { label: '退款金额', value: '¥240.00' },
       { label: '退款原因', value: '少件(含缺少配件)' },
-      { label: '退款说明', value: 'uudksjj' },
+      { label: '退款说明', value: '1111' },
     ],
     images: [
-      'https://img0.baidu.com/it/u=3025010615,2026135017&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
-      'https://img1.baidu.com/it/u=1067201099,2862800611&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
-      'https://img2.baidu.com/it/u=3025010615,2026135017&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
-      'https://img3.baidu.com/it/u=1067201099,2862800611&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
-      'https://img0.baidu.com/it/u=3025010615,2026135017&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
-    ]
-  }
+      'https://img2.baidu.com/it/u=1784082031,1597724266&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1360',
+      'https://img1.baidu.com/it/u=3479126922,3943933034&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=732',
+      'https://img2.baidu.com/it/u=1784082031,1597724266&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1360',
+      'https://img2.baidu.com/it/u=1784082031,1597724266&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1360',
+      'https://img2.baidu.com/it/u=1784082031,1597724266&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1360',
+    ],
+  },
 ];
 
 // 实际使用的数据 (如果有 props.data 则可进行转换,暂用 mock)
@@ -94,11 +94,23 @@ const displayHistory = computed(() => {
             </div>
           </div>
 
-          <!-- 图片凭证 -->
+          <!-- 图片凭证:点击任意一张,预览里可轮播 -->
           <div class="image-gallery" v-if="item.images && item.images.length > 0">
-            <div v-for="(img, iIdx) in item.images" :key="iIdx" class="image-wrapper">
-              <Image :src="img" :width="64" :height="64" style="object-fit: cover; border-radius: 4px;" />
-            </div>
+            <a-image-preview-group>
+              <div
+                v-for="(img, iIdx) in item.images"
+                :key="iIdx"
+                class="image-wrapper"
+              >
+                <a-image
+                  :src="img"
+                  :width="64"
+                  :height="64"
+                  :preview="true"
+                  style="object-fit: cover; border-radius: 4px"
+                />
+              </div>
+            </a-image-preview-group>
           </div>
         </div>
 
@@ -114,7 +126,7 @@ const displayHistory = computed(() => {
   background: #fff;
   padding: 20px;
   min-height: 400px;
-  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
 }
 
 .negotiation-list {
@@ -208,6 +220,8 @@ const displayHistory = computed(() => {
       display: flex;
       align-items: center;
       justify-content: center;
+      margin-right: 8px;
+      margin-bottom: 8px;
     }
   }
 }

+ 6 - 4
src/order/Review.vue

@@ -122,15 +122,17 @@ function handleCancel() {
           </div>
           <div class="info-item">
             <span class="label">退款说明:</span>
-            <span class="value">uudksjj</span>
+            <span class="value">111</span>
           </div>
         </div>
 
         <!-- 凭证图片 -->
         <div class="proof-images">
-          <div v-for="i in 5" :key="i" class="image-wrapper">
-            <Image src="https://img.yzcdn.cn/vant/cat.jpeg" :width="40" :height="40" />
-          </div>
+          <Image.PreviewGroup>
+            <div v-for="i in 5" :key="i" class="image-wrapper">
+              <Image :src="`https://img.yzcdn.cn/vant/cat.jpeg#${i}`" :width="40" :height="40" />
+            </div>
+          </Image.PreviewGroup>
         </div>
       </div>
 

+ 2 - 10
src/service/SingleItemDetail.vue

@@ -78,18 +78,10 @@ const canExpandLogistics = computed(() => logisticsTimeline.value.length > 2);
 function toggleLogisticsExpand() {
   showAllLogistics.value = !showAllLogistics.value;
 }
-const expressTypeText: Record<string, string> = {
-  '0': '邮政速递',
-  '1': '顺丰速运',
-  '2': '京东快递',
-  '3': '中通快递',
-  '4': '圆通速递',
-  '5': '申通快递',
-  '6': '韵达快递',
-  '7': '极兔速递',
-};
+
 // 包裹数据
 const mockPackageItems = computed(() => {
+  console.log(props.data as any,"props.data");
   return (props.data as any).sameExpress ?? []
 });
 // 商品状态映射 - 收货状态(实体商品使用)