张田田 há 3 meses atrás
pai
commit
7300241dcd

+ 1 - 0
@types/typed-router.d.ts

@@ -35,6 +35,7 @@ declare module 'vue-router/auto-routes' {
     '//healthy/education': RouteRecordInfo<'//healthy/education', '/healthy/education', Record<never, never>, Record<never, never>>,
     '//notify/manage': RouteRecordInfo<'//notify/manage', '/notify/manage', Record<never, never>, Record<never, never>>,
     '//online/onlineConsult': RouteRecordInfo<'//online/onlineConsult', '/online/onlineConsult', Record<never, never>, Record<never, never>>,
+    '//order/afterSale': RouteRecordInfo<'//order/afterSale', '/order/afterSale', Record<never, never>, Record<never, never>>,
     '//order/dispatchOrder': RouteRecordInfo<'//order/dispatchOrder', '/order/dispatchOrder', Record<never, never>, Record<never, never>>,
     '//order/management': RouteRecordInfo<'//order/management', '/order/management', Record<never, never>, Record<never, never>>,
     '//order/revenueSharing': RouteRecordInfo<'//order/revenueSharing', '/order/revenueSharing', Record<never, never>, Record<never, never>>,

+ 211 - 0
src/order/Amount.vue

@@ -0,0 +1,211 @@
+<script setup lang="ts">
+import { ref, computed, watch } from 'vue';
+import { Button, Input, message } from 'ant-design-vue';
+
+const props = defineProps<{
+  data?: any;
+}>();
+
+const emit = defineEmits<{
+  'submit': [amount: number];
+  'close': [];
+}>();
+
+// 初始申请金额
+const originalAmount = computed(() => {
+  return props.data?.refundAmount || 240.00;
+});
+
+// 最终退款金额 (双向绑定)
+const finalAmount = ref<string | number>(originalAmount.value.toFixed(2));
+
+// 监听数据变化同步初始值
+watch(() => props.data, (newVal) => {
+  if (newVal?.refundAmount) {
+    finalAmount.value = newVal.refundAmount.toFixed(2);
+  }
+}, { immediate: true });
+
+// 校验金额
+function handleAmountChange(e: any) {
+  const value = e.target.value;
+  // 只允许数字和小数点
+  const formatted = value.replace(/[^\d.]/g, '');
+  finalAmount.value = formatted;
+}
+
+function handleBlur() {
+  let val = parseFloat(finalAmount.value as string);
+  if (isNaN(val)) {
+    val = 0;
+  }
+  
+  // 不能超过商品原金额
+  if (val > originalAmount.value) {
+    message.warning(`退款金额不能超过申请金额 ¥${originalAmount.value.toFixed(2)}`);
+    val = originalAmount.value;
+  }
+  
+  finalAmount.value = val.toFixed(2);
+}
+
+// 提交
+function handleConfirm() {
+  const val = parseFloat(finalAmount.value as string);
+  if (isNaN(val) || val < 0) {
+    message.error('请输入有效的退款金额');
+    return;
+  }
+  emit('submit', val);
+}
+
+// 取消
+function handleCancel() {
+  emit('close');
+}
+</script>
+
+<template>
+  <div class="amount-popup-container">
+    <div class="amount-content">
+      <!-- 申请金额展示 -->
+      <div class="info-row apply-amount">
+        <span class="label">申请金额:</span>
+        <span class="symbol">¥</span>
+        <span class="value">{{ originalAmount.toFixed(2) }}</span>
+      </div>
+
+      <!-- 最终退款金额编辑 -->
+      <div class="info-row final-amount">
+        <span class="label">最终退款金额:</span>
+        <div class="input-wrapper">
+          <span class="symbol">¥</span>
+          <input 
+            type="text" 
+            v-model="finalAmount" 
+            class="amount-input"
+            @input="handleAmountChange"
+            @blur="handleBlur"
+          />
+        </div>
+      </div>
+
+      <!-- 提示文字 -->
+      <div class="note-text">
+        确认后,退款将退回原账户!
+      </div>
+
+      <!-- 底部按钮 -->
+      <div class="footer-actions">
+        <Button @click="handleCancel" class="btn-cancel">取消</Button>
+        <Button type="primary" @click="handleConfirm" class="btn-confirm">确定</Button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.amount-popup-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 40px 20px;
+  background: #fff;
+  min-height: 300px;
+}
+
+.amount-content {
+  width: 100%;
+  max-width: 400px;
+  text-align: center;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+  .info-row {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 24px;
+    
+    .label {
+      font-size: 16px;
+      color: #333;
+      font-weight: bold;
+    }
+
+    &.apply-amount {
+      .symbol {
+        font-size: 18px;
+        margin-left: 4px;
+        font-weight: bold;
+      }
+      .value {
+        font-size: 28px;
+        font-weight: bold;
+        color: #333;
+      }
+    }
+
+    &.final-amount {
+      .input-wrapper {
+        display: flex;
+        align-items: center;
+        border: 1px solid #d9d9d9;
+        border-radius: 2px;
+        padding: 4px 8px;
+        margin-left: 8px;
+        width: 140px;
+        
+        .symbol {
+          font-size: 16px;
+          color: #333;
+          margin-right: 4px;
+        }
+
+        .amount-input {
+          border: none;
+          outline: none;
+          width: 100%;
+          font-size: 20px;
+          color: #ff4d4f; // 红色文字
+          font-weight: bold;
+          text-align: left;
+        }
+      }
+    }
+  }
+
+  .note-text {
+    font-size: 15px;
+    color: #333;
+    margin-top: 8px;
+    margin-bottom: 40px;
+  }
+
+  .footer-actions {
+    display: flex;
+    gap: 40px;
+    justify-content: center;
+    width: 100%;
+
+    .ant-btn {
+      height: 36px;
+      min-width: 80px;
+      border-radius: 4px;
+      font-size: 14px;
+    }
+
+    .btn-cancel {
+      border-color: #1890ff;
+      color: #1890ff;
+      background: #fff;
+    }
+
+    .btn-confirm {
+      background-color: #2f74ff;
+      border-color: #2f74ff;
+    }
+  }
+}
+</style>

+ 524 - 0
src/order/LogisticsDetails.vue

@@ -0,0 +1,524 @@
+<script setup lang="ts">
+import { computed, ref } from 'vue';
+import { notification } from 'ant-design-vue';
+import { DownOutlined, UpOutlined, StarOutlined } from '@ant-design/icons-vue';
+
+const props = defineProps<{
+  data?: any;
+}>();
+
+const showAllLogistics = ref(false);
+
+const expressTypeText: Record<string, string> = {
+  '0': '邮政速递',
+  '1': '顺丰速运',
+  '2': '京东快递',
+  '3': '中通快递',
+  '4': '圆通速递',
+  '5': '申通快递',
+  '6': '韵达快递',
+  '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(() => {
+  return {
+    ...mockData,
+    // ...(props.data || {})
+  };
+});
+
+// 物流信息
+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 || '')
+  };
+});
+
+type LogisticsTimelineItem = {
+  title: string;
+  time: string;
+  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;
+  if (!time && !fallbackTitle && !detail) return null;
+  return {
+    title: fallbackTitle || '-',
+    time,
+    detail: detail && detail !== fallbackTitle ? detail : '',
+  };
+}
+
+const timelineData = computed<LogisticsTimelineItem[]>(() => {
+  const source = displayData.value;
+  const listCandidate =
+    source?.expressDetails ??
+    source?.logisticsDetails ??
+    source?.logisticsList ??
+    source?.expressRouteList ??
+    source?.expressRoutes ??
+    source?.traces ??
+    source?.traceList ??
+    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;
+});
+
+const visibleTimeline = computed(() => {
+  if (showAllLogistics.value) return timelineData.value;
+  return timelineData.value.slice(0, 2);
+});
+
+const canExpandLogistics = computed(() => timelineData.value.length > 2);
+
+function toggleLogisticsExpand() {
+  showAllLogistics.value = !showAllLogistics.value;
+}
+
+// 复制物流信息
+function handleCopyTracking() {
+  const trackingNumber = logisticsInfo.value.trackingNumber;
+  if (trackingNumber) {
+    navigator.clipboard.writeText(trackingNumber).then(() => {
+      notification.success({ message: '复制成功' });
+    }).catch(() => {
+      notification.error({ message: '复制失败' });
+    });
+  }
+}
+
+// 包裹商品
+const packageItems = computed(() => {
+  return displayData.value.sameExpress ?? []
+});
+</script>
+
+<template>
+  <div class="logistics-details-container">
+    <div v-if="displayData.receiptType === '0'">
+      <!-- 通道/单号 -->
+      <div class="header-line">
+        <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>
+
+      <!-- 物流线 -->
+      <div class="timeline-box">
+        <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>
+            <div class="line" v-if="index !== visibleTimeline.length - 1 || canExpandLogistics"></div>
+          </div>
+          <div class="content-col">
+            <div class="status-title" :class="{ 'text-active': index === 0 }">
+              <span class="title-text">{{ item.title }}</span>
+              <span class="time-text">{{ item.time }}</span>
+            </div>
+            <div class="status-detail">{{ item.detail }}</div>
+          </div>
+        </div>
+
+        <!-- 展开按钮 -->
+        <div class="expand-row" v-if="canExpandLogistics">
+          <div class="dot-col">
+            <span class="dot"></span>
+          </div>
+          <div class="content-col">
+            <a @click="toggleLogisticsExpand" class="expand-link">
+              {{ showAllLogistics ? '收起更多物流明细' : '展开更多物流明细' }}
+              <DownOutlined :class="{ 'rotate-180': showAllLogistics }" class="arrow-icon" />
+            </a>
+          </div>
+        </div>
+      </div>
+
+      <!-- 收货人行 -->
+      <div class="recipient-line">
+        <span class="receive-badge">收</span>
+        <span class="recipient-text">
+          {{ logisticsInfo.recipientName }}, {{ logisticsInfo.recipientPhone }}, {{ logisticsInfo.recipientAddress }}
+        </span>
+      </div>
+
+      <!-- 包裹商品 -->
+      <div class="package-items-wrapper" v-if="packageItems.length > 0">
+        <div class="package-header">
+          <StarOutlined class="star-icon" />
+          <span class="title">包裹内商品</span>
+        </div>
+        <div class="package-list">
+          <div class="package-item" v-for="(item, index) in packageItems" :key="index">
+            <div class="item-thumb">
+              <img v-if="item.conditioningProgramPhoto" :src="item.conditioningProgramPhoto" />
+              <div v-else class="img-placeholder">📦</div>
+            </div>
+            <div class="item-main">
+              <div class="item-name">{{ item.conditioningProgramName }}</div>
+              <div class="item-spec">{{ item.convertDose }}{{ item.convertUnit }}</div>
+            </div>
+            <div class="item-side">
+              <div class="item-price">¥{{ item.unitPrice }}</div>
+              <div class="item-qty">x{{ item.totalMeasure }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 线下 -->
+    <div v-else-if="displayData.receiptType === '1'">
+      <div class="offline-box">线下取货模式</div>
+    </div>
+
+    <div v-else class="empty-box">暂无物流数据</div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.logistics-details-container {
+  padding: 10px;
+  background: #fff;
+  color: #333;
+  font-size: 14px;
+}
+
+/* Header Line */
+.header-line {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+
+  .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;
+    }
+
+    .copy-link {
+      color: #999;
+      font-size: 13px;
+      cursor: pointer;
+      text-decoration: none;
+
+      &:hover {
+        color: #666;
+      }
+    }
+  }
+
+  .header-right {
+    color: #999;
+    font-size: 13px;
+  }
+}
+
+/* Timeline */
+.timeline-box {
+  margin-bottom: 24px;
+}
+
+.timeline-row {
+  display: flex;
+  gap: 12px;
+}
+
+.dot-col {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 16px;
+
+  .dot {
+    width: 10px;
+    height: 10px;
+    background: #e8e8e8;
+    border-radius: 50%;
+    margin-top: 6px;
+    flex-shrink: 0;
+  }
+
+  .dot-active {
+    background: #FF6A00;
+    box-shadow: 0 0 0 4px rgba(255, 106, 0, 0.1);
+  }
+
+  .line {
+    width: 1px;
+    background: #f0f0f0;
+    flex: 1;
+    min-height: 40px;
+  }
+}
+
+.content-col {
+  flex: 1;
+  padding-bottom: 24px;
+
+  .status-title {
+    margin-bottom: 8px;
+    font-size: 15px;
+    display: flex;
+    gap: 10px;
+    color: #666;
+
+    &.text-active {
+      color: #FF6A00;
+      font-weight: 500;
+    }
+
+    .time-text {
+      font-size: 14px;
+    }
+  }
+
+  .status-detail {
+    font-size: 14px;
+    color: #999;
+    line-height: 1.6;
+  }
+}
+
+.expand-row {
+  display: flex;
+  gap: 12px;
+  margin-top: -10px;
+
+  .expand-link {
+    color: #666;
+    font-size: 14px;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+
+    &:hover {
+      color: #333;
+    }
+
+    .arrow-icon {
+      font-size: 10px;
+      transition: transform 0.3s;
+    }
+    .rotate-180 {
+      transform: rotate(180deg);
+    }
+  }
+}
+
+/* Recipient */
+.recipient-line {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 24px;
+
+  .receive-badge {
+    width: 20px;
+    height: 20px;
+    background: #fff1f0;
+    border: 1px solid #ff4d4f;
+    color: #ff4d4f;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 12px;
+    flex-shrink: 0;
+  }
+
+  .recipient-text {
+    font-weight: 500;
+    color: #333;
+  }
+}
+
+/* Package Items */
+.package-items-wrapper {
+  border: 1px solid #e8e8e8;
+  border-radius: 2px;
+  padding: 16px;
+
+  .package-header {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    margin-bottom: 16px;
+
+    .star-icon {
+      color: #ccc;
+      font-size: 18px;
+    }
+
+    .title {
+      font-weight: bold;
+      font-size: 15px;
+    }
+  }
+}
+
+.package-item {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  padding: 12px 0;
+  
+  &:not(:last-child) {
+    border-bottom: 1px solid #f5f5f5;
+  }
+
+  .item-thumb {
+    width: 60px;
+    height: 60px;
+    background: #f5f5f5;
+    border-radius: 4px;
+    overflow: hidden;
+    flex-shrink: 0;
+
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+    
+    .img-placeholder {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      height: 100%;
+      font-size: 24px;
+    }
+  }
+
+  .item-main {
+    flex: 1;
+
+    .item-name {
+      font-weight: 500;
+      font-size: 15px;
+      margin-bottom: 6px;
+    }
+
+    .item-spec {
+      color: #999;
+      font-size: 13px;
+    }
+  }
+
+  .item-side {
+    text-align: right;
+
+    .item-price {
+      font-size: 15px;
+      margin-bottom: 4px;
+    }
+
+    .item-qty {
+      color: #999;
+      font-size: 14px;
+    }
+  }
+}
+
+.offline-box, .empty-box {
+  padding: 40px;
+  text-align: center;
+  color: #999;
+}
+</style>

+ 222 - 0
src/order/Negotiations.vue

@@ -0,0 +1,222 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { Avatar, Image } from 'ant-design-vue';
+
+const props = defineProps<{
+  data?: any;
+}>();
+
+// 协商历史条目类型
+interface NegotiationItem {
+  avatar: string;
+  name: string;
+  time: string;
+  action: string;
+  title?: string;
+  content?: string;
+  fields?: { label: string; value: string }[];
+  images?: string[];
+}
+
+// 默认假数据 (模拟图片中的内容)
+const mockNegotiationHistory: NegotiationItem[] = [
+  {
+    avatar: 'https://img.yzcdn.cn/vant/cat.jpeg',
+    name: '谢芳',
+    time: '2025-02-19 14:30:35',
+    action: '撤销退款申请',
+  },
+  {
+    avatar: 'https://img0.baidu.com/it/u=670138986,218049280&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
+    name: '平台',
+    time: '2025-02-19 14:30:35',
+    action: '平台拒绝了申请',
+    title: '平台拒绝了申请',
+    content: '请选择原因为与卖家协商一致',
+  },
+  {
+    avatar: 'https://img.yzcdn.cn/vant/cat.jpeg',
+    name: '谢芳',
+    time: '2025-02-19 14:30:27',
+    action: '发起了退款申请',
+    fields: [
+      { label: '商品状态', value: '未收货' },
+      { label: '退款类型', value: '退款' },
+      { label: '退款金额', value: '¥240.00' },
+      { label: '退款原因', value: '少件(含缺少配件)' },
+      { label: '退款说明', value: 'uudksjj' },
+    ],
+    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',
+    ]
+  }
+];
+
+// 实际使用的数据 (如果有 props.data 则可进行转换,暂用 mock)
+const displayHistory = computed(() => {
+  return mockNegotiationHistory;
+});
+</script>
+
+<template>
+  <div class="negotiations-container">
+    <div class="negotiation-list">
+      <div v-for="(item, index) in displayHistory" :key="index" class="negotiation-item">
+        <!-- 头部信息: 头像、名称、时间 -->
+        <div class="item-header">
+          <Avatar :src="item.avatar" :size="40" class="user-avatar" />
+          <div class="header-info">
+            <span class="user-name">{{ item.name }}</span>
+            <span class="action-time">{{ item.time }}</span>
+          </div>
+        </div>
+
+        <!-- 内容区域 -->
+        <div class="item-content">
+          <!-- 简单动作标题 -->
+          <div class="action-title" v-if="item.action">{{ item.action }}</div>
+
+          <!-- 平台拒绝等详情 -->
+          <div class="detail-box" v-if="item.title || item.content">
+            <div class="detail-row" v-if="item.title">【标题】{{ item.title }}</div>
+            <div class="detail-row" v-if="item.content">【内容】{{ item.content }}</div>
+          </div>
+
+          <!-- 发起申请详情 -->
+          <div class="fields-list" v-if="item.fields">
+            <div v-for="(field, fIdx) in item.fields" :key="fIdx" class="field-item">
+              <span class="field-label">{{ field.label }}:</span>
+              <span class="field-value">{{ field.value }}</span>
+            </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>
+          </div>
+        </div>
+
+        <!-- 分割线 (最后一条不显示) -->
+        <div class="item-divider" v-if="index !== displayHistory.length - 1"></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.negotiations-container {
+  background: #fff;
+  padding: 20px;
+  min-height: 400px;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
+}
+
+.negotiation-list {
+  display: flex;
+  flex-direction: column;
+}
+
+.negotiation-item {
+  padding-bottom: 24px;
+}
+
+.item-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 16px;
+
+  .header-info {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    gap: 32px;
+  }
+
+  .user-name {
+    font-weight: 600;
+    font-size: 15px;
+    color: #333;
+  }
+
+  .action-time {
+    font-size: 13px;
+    color: #999;
+  }
+}
+
+.item-content {
+  padding-left: 52px; // 40px avatar + 12px gap
+
+  .action-title {
+    font-weight: bold;
+    font-size: 15px;
+    color: #333;
+    margin-bottom: 12px;
+  }
+
+  .detail-box {
+    margin-bottom: 12px;
+    .detail-row {
+      font-size: 14px;
+      line-height: 1.8;
+      color: #333;
+    }
+  }
+
+  .fields-list {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+    margin-bottom: 16px;
+
+    .field-item {
+      display: flex;
+      font-size: 14px;
+      line-height: 1.6;
+
+      .field-label {
+        color: #333;
+        width: 70px;
+        flex-shrink: 0;
+      }
+
+      .field-value {
+        color: #333;
+      }
+    }
+  }
+
+  .image-gallery {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+
+    .image-wrapper {
+      border: 1px solid #f0f0f0;
+      border-radius: 4px;
+      overflow: hidden;
+      background: #fafafa;
+      width: 64px;
+      height: 64px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+  }
+}
+
+.item-divider {
+  height: 1px;
+  background-color: #f0f0f0;
+  margin-top: 24px;
+  margin-left: -20px;
+  margin-right: -20px;
+}
+</style>

+ 405 - 0
src/order/Review.vue

@@ -0,0 +1,405 @@
+<script setup lang="ts">
+import { ref, computed, h } from 'vue';
+import { Avatar, Image, Radio, Button, Input } from 'ant-design-vue';
+import { VxeUI } from 'vxe-pc-ui';
+import Negotiations from './Negotiations.vue';
+import LogisticsDetails from './LogisticsDetails.vue';
+const props = defineProps<{
+  data?: any;
+}>();
+
+const emit = defineEmits<{
+  'submit': [status: string, reason: string];
+  'close': [];
+}>();
+
+// 是否收起协商历史
+const isNegotiationCollapsed = ref(false);
+
+// 审核决策: 'agree' | 'reject'
+const reviewStatus = ref('agree');
+// 理由
+const reviewReason = ref('');
+
+// 模拟顶部的商品数据
+const productInfo = computed(() => {
+  return {
+    name: props.data?.productName || '肝血虚穴位贴',
+    price: 30,
+    qty: 8,
+    totalPrice: 240,
+    specs: '10贴',
+    imageUrl: 'https://img.yzcdn.cn/vant/cat.jpeg'
+  };
+});
+
+// 处理提交
+function handleConfirm() {
+  emit('submit', reviewStatus.value, reviewReason.value);
+}
+
+// 查看物流
+function viewLogistics() {
+  VxeUI.modal.open({
+    title: '物流详情',
+    height: 900,
+    width: 1000,
+    escClosable: true,
+    destroyOnClose: true,
+    id: 'logistics-modal',
+    slots: {
+      default() {
+        return h(LogisticsDetails, {
+          data: props.data
+        });
+      }
+    }
+  });
+}
+
+// 处理取消
+function handleCancel() {
+  emit('close');
+}
+</script>
+
+<template>
+  <div class="review-page-container">
+    <!-- 商品信息头部 -->
+    <div class="product-info-section">
+      <div class="product-card">
+        <Image :src="productInfo.imageUrl" :width="70" :height="70" class="product-image" />
+        <div class="product-details">
+          <div class="detail-row header-row">
+            <span class="product-name">{{ productInfo.name }}</span>
+            <span class="product-price">¥{{ productInfo.price }}</span>
+          </div>
+          <div class="detail-row sub-row">
+            <span class="product-specs">{{ productInfo.specs }}</span>
+            <span class="product-qty">x{{ productInfo.qty }}</span>
+          </div>
+          <div class="product-total">
+            <span>商品总价:</span>
+            <span class="price-val">¥{{ productInfo.totalPrice }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="content-divider"></div>
+
+    <!-- 中部内容分栏 -->
+    <div class="middle-columns">
+      <!-- 左侧:申请信息 -->
+      <div class="refund-info-column">
+        <div class="info-list">
+          <div class="info-item">
+            <span class="label">申请时间:</span>
+            <span class="value">2025-02-19 14:30:27</span>
+          </div>
+          <div class="info-item">
+            <span class="label">商品状态:</span>
+            <span class="value">未收货</span>
+          </div>
+          <div class="info-item methods-item">
+            <span class="label">发货方式:</span>
+            <span class="value">线下取货</span>
+            <span class="label method-label">发货方式:</span>
+            <span class="value">配送</span>
+            <a href="javascript:;" class="shipping-link" @click="viewLogistics">运输中</a>
+          </div>
+          <div class="info-item">
+            <span class="label">退款类型:</span>
+            <span class="value">退款</span>
+          </div>
+          <div class="info-item">
+            <span class="label">退款金额:</span>
+            <span class="value amount-value">¥240.00</span>
+          </div>
+          <div class="info-item">
+            <span class="label">退款原因:</span>
+            <span class="value">少件(含缺少配件)</span>
+          </div>
+          <div class="info-item">
+            <span class="label">退款说明:</span>
+            <span class="value">uudksjj</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>
+        </div>
+      </div>
+
+      <!-- 中间竖向分割线 -->
+      <div class="vertical-divider"></div>
+
+      <!-- 右侧:协商历史 -->
+      <div class="negotiation-column">
+        <div class="column-header">
+          <span class="title">协商历史</span>
+          <a href="javascript:;" class="collapse-toggle" @click="isNegotiationCollapsed = !isNegotiationCollapsed">
+            {{ isNegotiationCollapsed ? '展开' : '收起' }}
+          </a>
+        </div>
+        <div class="negotiation-content" v-show="!isNegotiationCollapsed">
+          <Negotiations />
+        </div>
+      </div>
+    </div>
+
+    <div class="content-divider" style="margin-top: 0;"></div>
+
+    <!-- 底部:审核区 -->
+    <div class="review-action-section">
+      <div class="action-title">审核</div>
+      <div class="review-form">
+        <div class="radio-row">
+          <Radio.Group v-model:value="reviewStatus">
+            <Radio value="agree">同意</Radio>
+            <Radio value="reject">拒绝</Radio>
+          </Radio.Group>
+        </div>
+        <div class="reason-row" v-if="reviewStatus === 'reject'">
+          <div class="reason-label">拒绝理由</div>
+          <Input.TextArea 
+            v-model:value="reviewReason" 
+            placeholder="请输入拒绝退款的理由" 
+            :rows="4" />
+        </div>
+      </div>
+      <div class="action-buttons">
+        <Button @click="handleCancel">取消</Button>
+        <Button type="primary" @click="handleConfirm" class="confirm-btn">确定</Button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.review-page-container {
+  background: #fff;
+  display: flex;
+  flex-direction: column;
+  color: #333;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
+  overflow: hidden;
+}
+
+.product-info-section {
+  padding: 16px 20px;
+
+  .product-card {
+    display: flex;
+    gap: 16px;
+
+    .product-image {
+      border: 1px solid #f0f0f0;
+      border-radius: 4px;
+    }
+
+    .product-details {
+      flex: 1;
+
+      .detail-row {
+        display: flex;
+        justify-content: space-between;
+        margin-bottom: 4px;
+        width: 20%;
+        
+        &.header-row {
+          .product-name {
+            font-size: 14px;
+            font-weight: bold;
+          }
+          .product-price {
+            font-size: 14px;
+            color: #333;
+          }
+        }
+
+        &.sub-row {
+          .product-specs {
+            color: #999;
+            font-size: 12px;
+          }
+          .product-qty {
+            color: #666;
+            font-size: 13px;
+          }
+        }
+      }
+
+      .product-total {
+        margin-top: 8px;
+        text-align: left;
+        font-size: 14px;
+        .price-val {
+          font-size: 20px;
+          font-weight: bold;
+          color: #333;
+        }
+      }
+    }
+  }
+}
+
+.content-divider {
+  height: 1px;
+  background-color: #f0f0f0;
+  margin: 0 20px;
+}
+
+.middle-columns {
+  display: flex;
+  padding: 20px;
+  min-height: 380px;
+
+  .refund-info-column {
+    flex: 1;
+    padding-right: 20px;
+
+    .info-list {
+      display: flex;
+      flex-direction: column;
+      gap: 8px;
+
+      .info-item {
+        font-size: 13px;
+        display: flex;
+        align-items: baseline;
+
+        .label {
+          color: #333;
+          width: 70px;
+          flex-shrink: 0;
+        }
+
+        .value {
+          color: #333;
+        }
+
+        &.methods-item {
+          .method-label {
+            margin-left: 20px;
+          }
+          .shipping-link {
+            color: #1890ff;
+            margin-left: 12px;
+            text-decoration: none;
+            &:hover {
+              text-decoration: underline;
+            }
+          }
+        }
+
+        .amount-value {
+          color: #ff4d4f;
+          font-size: 18px;
+          font-weight: bold;
+        }
+      }
+    }
+
+    .proof-images {
+      display: flex;
+      gap: 10px;
+      margin-top: 20px;
+
+      .image-wrapper {
+        border: 1px solid #f0f0f0;
+        border-radius: 4px;
+        overflow: hidden;
+        background: #fafafa;
+        width: 40px;
+        height: 40px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+    }
+  }
+
+  .vertical-divider {
+    width: 1px;
+    background-color: #f0f0f0;
+    margin: 0 10px;
+  }
+
+  .negotiation-column {
+    flex: 1;
+    padding-left: 20px;
+    display: flex;
+    flex-direction: column;
+
+    .column-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 12px;
+
+      .title {
+        font-weight: bold;
+        font-size: 14px;
+      }
+
+      .collapse-toggle {
+        font-size: 13px;
+        color: #1890ff;
+        cursor: pointer;
+        text-decoration: none;
+      }
+    }
+
+    .negotiation-content {
+      flex: 1;
+      overflow-y: auto;
+      max-height: 340px;
+      
+      :deep(.negotiations-container) {
+        padding: 5px 0;
+      }
+    }
+  }
+}
+
+.review-action-section {
+  padding: 24px 20px;
+  border-top: 1px solid #f0f0f0;
+  background: #fff;
+
+  .action-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 20px;
+  }
+
+  .review-form {
+    .radio-row {
+      margin-bottom: 16px;
+    }
+
+    .reason-row {
+      .reason-label {
+        font-size: 14px;
+        margin-bottom: 12px;
+        font-weight: 500;
+      }
+    }
+  }
+
+  .action-buttons {
+    display: flex;
+    justify-content: flex-end;
+    gap: 12px;
+    margin-top: 24px;
+
+    .confirm-btn {
+      min-width: 80px;
+    }
+  }
+}
+</style>

+ 93 - 0
src/order/SeeAmount.vue

@@ -0,0 +1,93 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+
+const props = defineProps<{
+  data?: any;
+}>();
+
+const originalAmount = computed(() => {
+  return props.data?.refundAmount || 240.00;
+});
+
+const finalAmount = computed(() => {
+  return props.data?.finalRefundAmount || props.data?.refundAmount || 240.00;
+});
+</script>
+
+<template>
+  <div class="see-amount-container">
+    <div class="amount-content">
+      <!-- 申请金额展示 -->
+      <div class="info-row apply-amount">
+        <span class="label">申请金额:</span>
+        <span class="symbol">¥</span>
+        <span class="value">{{ originalAmount.toFixed(2) }}</span>
+      </div>
+
+      <!-- 最终退款金额展示 -->
+      <div class="info-row final-amount">
+        <span class="label">最终退款金额:</span>
+        <span class="symbol">¥</span>
+        <span class="value">{{ finalAmount.toFixed(2) }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.see-amount-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 60px 20px;
+  background: #fff;
+  min-height: 240px;
+}
+
+.amount-content {
+  width: 100%;
+  max-width: 400px;
+  text-align: center;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+  .info-row {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 24px;
+    
+    .label {
+      font-size: 16px;
+      color: #333;
+      font-weight: bold;
+    }
+
+    .symbol {
+      font-size: 18px;
+      margin-left: 4px;
+      font-weight: bold;
+    }
+
+    &.apply-amount {
+      .value {
+        font-size: 28px;
+        font-weight: bold;
+        color: #333;
+      }
+    }
+
+    &.final-amount {
+      .symbol {
+        color: #ff4d4f;
+      }
+      .value {
+        font-size: 28px;
+        font-weight: bold;
+        color: #ff4d4f; // 红色文字
+      }
+    }
+  }
+}
+</style>

+ 466 - 0
src/pages/index/order/afterSale.vue

@@ -0,0 +1,466 @@
+<script setup lang="ts">
+import { h, ref, reactive, shallowRef, onMounted, toRaw, computed } from 'vue';
+import EditShipment from '@/order/EditShipment.vue';
+import OrderDetail from '@/service/OrderDetail.vue';
+import LogisticsDetails from '@/order/LogisticsDetails.vue';
+import Negotiations from '@/order/Negotiations.vue';
+import SeeAmount from '@/order/SeeAmount.vue';
+import Amount from '@/order/Amount.vue';
+import Review from '@/order/Review.vue';
+import type { ShipmentModel, ShipmentQuery } from '@/model/order.model';
+import { getAllSupplierMethod } from '@/request/api/care.api';
+defineOptions({
+  name: 'ShipmentPage',
+});
+import { receiptStatus, receiptType } from '@/model/options';
+import dayjs from 'dayjs';
+// 接口数据
+import {
+  getShipmentListMethod,
+} from '@/request/api/order.api';
+import { usePagination } from 'alova/client';
+
+import {
+  type VxeFormListeners,
+  type VxeFormProps,
+  VxeUI,
+} from 'vxe-pc-ui';
+import type {
+  VxeGridInstance,
+  VxeGridListeners,
+  VxeGridProps,
+} from 'vxe-table';
+
+
+const model = shallowRef<ShipmentQuery>();
+const searchFormProps = reactive<VxeFormProps<ShipmentQuery>>({
+  titleWidth: 100,
+  titleAlign: 'right',
+  titleColon: true,
+  data: {
+    receiptStatus: '0', // 默认待发货
+  },
+  items: [
+    {
+      field: 'orderNo',
+      title: '订单编号',
+      span: 4,
+      itemRender: { name: 'VxeInput', props: { placeholder: '请输入' } },
+    },
+    {
+      field: 'receiptStatus',
+      title: '收货人',
+      span: 4,
+
+      itemRender: {
+        name: 'VxeSelect',
+        options: receiptStatus,
+        props: { placeholder: '请选择', clearable: true },
+      },
+    },
+    {
+      field: 'receiptType',
+      title: '退款状态',
+      span: 4,
+      itemRender: {
+        name: 'VxeSelect',
+        options: receiptType,
+        props: { placeholder: '请选择' },
+      },
+    },
+
+    {
+      span: 8,
+      itemRender: {
+        name: 'VxeButtonGroup',
+        options: [
+          { name: 'submits', type: 'submit', content: '查询', status: 'primary' },
+          { name: 'reset', type: 'reset', content: '重置' },
+        ],
+        events: {},
+      },
+    },
+  ],
+});
+const searchFormEmits: VxeFormListeners<ShipmentQuery> = {
+  submit({ data }) {
+    model.value = {
+      ...data,
+      payTimeStart: payTimeStart.value ? dayjs(payTimeStart.value).format('YYYY-MM-DD HH:mm:ss') : '',
+      payTimeEnd: payTimeEnd.value ? dayjs(payTimeEnd.value).format('YYYY-MM-DD HH:mm:ss') : '',
+    } as any;
+  },
+  // 重置
+  reset({ data }) {
+    model.value = { ...data } as any;
+    payTimeStart.value = '';
+    payTimeEnd.value = '';
+  },
+};
+const processedData = ref<ShipmentModel[]>([]);
+const orderGroupMap = ref<Map<string, { startIndex: number; count: number; rows: ShipmentModel[] }>>(new Map());
+
+function getGroupKey(item: ShipmentModel): string {
+  const orderNo = item.orderNo || '';
+  const supplierId = item.conditioningProgramSupplierId || '';
+  return `${orderNo}_${supplierId}`;
+}
+
+function processDataForMerge(data: ShipmentModel[]) {
+  const grouped = new Map<string, ShipmentModel[]>();
+  const result: ShipmentModel[] = [];
+
+  data.forEach((item) => {
+    const groupKey = getGroupKey(item);
+    if (!grouped.has(groupKey)) {
+      grouped.set(groupKey, []);
+    }
+    grouped.get(groupKey)!.push(item);
+  });
+
+  orderGroupMap.value.clear();
+  let currentIndex = 0;
+
+  grouped.forEach((rows, groupKey) => {
+    const count = rows.length;
+    orderGroupMap.value.set(groupKey, {
+      startIndex: currentIndex,
+      count,
+      rows,
+    });
+    result.push(...rows);
+    currentIndex += count;
+  });
+
+  processedData.value = result;
+  return result;
+}
+
+const gridRef = ref<VxeGridInstance<ShipmentModel>>();
+const gridOptions = reactive<VxeGridProps<ShipmentModel>>({
+  id: 'tag-list',
+  border: true,
+  showOverflow: true,
+  height: 'auto',
+  autoResize: false,
+  syncResize: true,
+  scrollY: { enabled: false },
+  spanMethod: ({ row, column, rowIndex }: any) => {
+    const groupKey = getGroupKey(row);
+    const groupInfo = orderGroupMap.value.get(groupKey);
+
+    if (!groupInfo) return { rowspan: 1, colspan: 1 };
+
+    const { startIndex, count } = groupInfo;
+    const isFirstRow = rowIndex === startIndex;
+
+    if (column.field === 'orderNo') {
+      return isFirstRow ? { rowspan: count, colspan: 1 } : { rowspan: 0, colspan: 0 };
+    }
+
+    if (column.field === 'action') {
+      return isFirstRow ? { rowspan: count, colspan: 1 } : { rowspan: 0, colspan: 0 };
+    }
+
+    return { rowspan: 1, colspan: 1 };
+  },
+  rowConfig: {
+    isHover: true,
+    isCurrent: true,
+  },
+  toolbarConfig: {
+    custom: true,
+    zoom: true,
+    slots: {
+      // buttons: 'handle',
+      tools: 'toolbar-extra',
+    },
+  },
+  columnConfig: {
+    resizable: true,
+  },
+  customConfig: {
+    storage: true,
+  },
+  columns: [
+    { type: 'seq', width: 70, fixed: 'left' },
+    { field: 'orderNo', title: '订单编号', slots: { default: 'orderNoCell' } },
+    { field: 'payTime', title: '售后类型' },
+    { field: 'totalMeasure', title: '退款更新时间' },
+    { field: 'conditioningProgramName', title: '项目名称' },
+    { field: 'totalMeasure', title: '商品类型' },
+    { field: 'totalPrice', title: '总价(元)' },
+    { field: 'pricingUnit', title: '收货人' },
+    { field: 'unitPrice', title: '退款状态' },
+    { field: 'receiptStatus', title: '物流状态', slots: { default: 'progressCell' } },
+    { field: 'receiptType', title: '核销情况', slots: { default: 'shipmentTypeCell' } },
+    { field: 'conditioningProgramSupplierName', title: '退款金额' },
+    { field: 'conditioningProgramSupplierName', title: '退款原因' },
+    { field: 'conditioningProgramSupplierName', title: '退款说明', slots: { default: 'refundReasonCell' } },
+    {
+      field: 'action',
+      title: '操作',
+      align: 'center',
+      width: 200,
+      showOverflow: false,
+      slots: { default: 'action' },
+    },
+  ],
+  data: [],
+});
+const gridEvents: VxeGridListeners = {};
+
+const { loading, page, pageSize, total, onSuccess, refresh } = usePagination(
+  (page, size) => getShipmentListMethod(page, size, model.value),
+  {
+    initialData: { data: [], total: 0 },
+    initialPage: 1,
+    initialPageSize: 100,
+    watchingStates: [model],
+    immediate: false,
+  }
+);
+onSuccess(({ data: { data } }) => {
+  const processed = processDataForMerge(data);
+  gridRef.value?.loadData(processed);
+});
+const supplierOptions = ref<{ label: string; value: string }[]>([]);
+const supplierArr = ref<any[]>([]);
+// 获取所有的供应商
+async function getSupplier(params: any) {
+  supplierOptions.value = [];
+  const res = (await getAllSupplierMethod(params)) as any[];
+  if (Array.isArray(res) && res.length > 0) {
+    supplierArr.value = res;
+    supplierOptions.value = res.map((item: any) => ({
+      label: item.name,
+      value: item.id,
+    }));
+  }
+}
+
+onMounted(() => {
+  model.value = toRaw(searchFormProps.data);
+  getSupplier({});
+});
+
+
+// 点击订单编号查看订单详情
+function serviceDetail(row?: ShipmentModel) {
+  if (!row) return;
+  const types = 'record';
+  VxeUI.modal.open({
+    id: 'servicePackageDetail-modal',
+    title: '调养记录详情',
+    height: window.innerHeight,
+    width: window.innerWidth,
+    fullscreen: true,
+    escClosable: true,
+    destroyOnClose: true,
+    slots: {
+      default() {
+        return h(OrderDetail, {
+          data: {
+            ...row,
+            id: row.id,
+            types,
+          },
+          onVoidSubmit() {
+            refresh(page.value);
+          },
+        } as any);
+      },
+    },
+  });
+}
+
+async function goShipment(row?: ShipmentModel) {
+  if (!row) return;
+
+  const isReview = row.receiptStatus === '0'; // 待发货 -> 审核
+  const isEditAmount = row.receiptStatus === '1'; // 已发货 -> 金额
+  if (isReview) {
+    VxeUI.modal.open({
+      title: '审核',
+      height: 900,
+      width: 1200,
+      escClosable: true,
+      destroyOnClose: true,
+      id: `review-modal`,
+      remember: true,
+      storage: true,
+      slots: {
+        default() {
+          return h(Review, {
+            data: row,
+            onSubmit: () => {
+              refresh(page.value);
+              VxeUI.modal.close('review-modal');
+            },
+            onClose: () => {
+              VxeUI.modal.close('review-modal');
+            }
+          });
+        }
+      }
+    });
+  } else {
+    // 金额 or 查看金额
+    const title = isEditAmount ? '退款金额' : '退款金额查看';
+    const component = isEditAmount ? Amount : SeeAmount;
+    
+    VxeUI.modal.open({
+      title,
+      height: 500,
+      width: 600,
+      escClosable: true,
+      destroyOnClose: true,
+      id: isEditAmount ? 'amount-edit-modal' : 'amount-see-modal',
+      slots: {
+        default() {
+          return h(component, {
+            data: row,
+            onSubmit: (amount: number) => {
+              console.log('Final Amount:', amount);
+              // TODO: API call to update amount
+              refresh(page.value);
+              VxeUI.modal.close('amount-edit-modal');
+            },
+            onClose: () => {
+              VxeUI.modal.close(isEditAmount ? 'amount-edit-modal' : 'amount-see-modal');
+            }
+          });
+        }
+      }
+    });
+  }
+}
+
+// 查看物流详情
+function viewLogistics(row?: ShipmentModel) {
+  if (!row) return;
+  VxeUI.modal.open({
+    title: '物流详情',
+    height: 800,
+    width: 1000,
+    escClosable: true,
+    destroyOnClose: true,
+    id: 'logistics-modal',
+    slots: {
+      default() {
+        return h(LogisticsDetails, {
+              data: row
+        });
+      }
+    }
+  });
+}
+
+function goAfterSale(row?: ShipmentModel) {
+  if (!row) return;
+  // TODO: Implement after sale details or history
+  console.log('goAfterSale', row);
+  VxeUI.modal.open({
+    title: '协商历史',
+    height: 800,
+    width: 1000,
+    escClosable: true,
+    destroyOnClose: true,
+    id: 'negotiations-modal',
+    slots: {
+      default() {
+        return h(Negotiations, {
+              data: row
+        });
+      }
+    }
+  }); 
+}
+
+const payTimeStart = ref<string>('');
+const payTimeEnd = ref<string>('');
+function disabledPayEndDate(current: any) {
+  if (!payTimeStart.value) return false;
+  return current && current < dayjs(payTimeStart.value);
+}
+</script>
+<template>
+  <div class="page-container flex flex-col">
+    <header class="flex-none mt-4">
+      <vxe-form v-bind="searchFormProps" v-on="searchFormEmits">
+        <template #payTimeRange>
+          <div class="date-range-container">
+            <a-date-picker v-model:value="payTimeStart" placeholder="请选择开始时间" style="flex: 1"
+              :show-time="{ format: 'HH:mm' }" />
+            <span class="date-separator">至</span>
+            <a-date-picker v-model:value="payTimeEnd" placeholder="请选择结束时间" style="flex: 1"
+              :disabledDate="disabledPayEndDate" :show-time="{ format: 'HH:mm' }" />
+          </div>
+        </template>
+      </vxe-form>
+    </header>
+    <main class="flex-auto overflow-hidden">
+      <vxe-grid ref="gridRef" v-bind="gridOptions" v-on="gridEvents" :loading="loading">
+        <template #orderNoCell="{ row }">
+          <span class="order-no-link" @click="serviceDetail(row)">{{ row.orderNo }}</span>
+        </template>
+        <template #progressCell="{ row }">
+          <span class="order-no-link" @click="viewLogistics(row)">{{ row.receiptStatus === '0' ? '待发货' : row.receiptStatus === '1' ? '已发货' : row.receiptStatus === '2' ?
+            '已收货' : '' }}</span>
+        </template>
+        <template #shipmentTypeCell="{ row }">
+          <div>{{ row.receiptType === '0' ? '配送' : row.receiptType === '1' ? '线下取货' : '' }}</div>
+        </template>
+        <template #refundReasonCell="{ row }">
+          <span class="order-no-link" @click="goAfterSale(row)">{{ row.conditioningProgramSupplierName }}</span>
+        </template>
+        <template #action="{ row }">
+          <vxe-button mode="text" status="primary" @click="goAfterSale(row)">
+            协商历史
+          </vxe-button>
+          <vxe-button mode="text" status="primary" @click="goShipment(row)">
+            {{ row.receiptStatus === '0' ? '审核' : row.receiptStatus === '1' ? '金额' : '查看金额' }}
+          </vxe-button>
+        </template>
+        <template #toolbar-extra>
+          <vxe-button style="margin-right: 12px" icon="vxe-icon-repeat" circle @click="refresh(page)"></vxe-button>
+        </template>
+      </vxe-grid>
+    </main>
+    <footer class="flex-none">
+      <vxe-pager v-model:current-page="page" v-model:page-size="pageSize" :total="total" :layouts="[
+        'Home',
+        'PrevJump',
+        'PrevPage',
+        'Number',
+        'NextPage',
+        'NextJump',
+        'End',
+        'Sizes',
+        'FullJump',
+        'Total',
+      ]" />
+    </footer>
+  </div>
+</template>
+<style scoped lang="scss">
+.date-separator {
+  margin: 0 10px;
+}
+
+.page-container {
+  padding: 0 24px;
+  max-height: var(--page-main-container);
+}
+
+.order-no-link {
+  color: #1890ff;
+  cursor: pointer;
+  text-decoration: none;
+
+  &:hover {
+    text-decoration: underline;
+    color: #40a9ff;
+  }
+}
+</style>

+ 6 - 10
src/request/api/account.api.ts

@@ -69,16 +69,12 @@ export function getMenusMethod(account: AccountModel) {
   return request.Get<AccountModel, any[]>(`/system/menu/getRouters`, {
     headers: { Authorization: account.token },
     transform(data) {
-      // data[5].children.push(
-      //   {
-      //     path: 'shipment',
-      //     meta: { title: '发货' },
-      //   },
-      //   {
-      //     path: 'revenueSharing',
-      //     meta: { title: '分账' },
-      //   },
-      // );
+      data[5].children.push(
+        {
+          path: 'afterSale',
+          meta: { title: '售后' },
+        },
+      );
       //   console.log(data, 'push之后的data', transformMenus(data));
       return { ...account, menus: transformMenus(data) };
     },

+ 1 - 0
src/router/index.ts

@@ -89,6 +89,7 @@ const router = createRouter({
             { path: 'dispatchOrder', component: () => import(`@/pages/index/order/dispatchOrder.vue`) },
             { path: 'shipment', component: () => import(`@/pages/index/order/shipment.vue`) },
             { path: 'revenueSharing', component: () => import(`@/pages/index/order/revenueSharing.vue`) },
+            { path: 'afterSale', component: () => import(`@/pages/index/order/afterSale.vue`) },
           ],
         },
       ],