OrdersPage.vue 9.42 KB
<!--
 * @Date: 2025-03-21
 * @Description: 我的订单页面
-->
<template>
  <AppLayout title="我的订单">
    <div class="bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen">
      <!-- 订单列表 -->
      <van-list
        v-model:loading="loading"
        :finished="finished"
        finished-text="没有更多了"
        @load="onLoad"
        class="px-4 py-3 space-y-4"
      >
        <FrostedGlass
          v-for="order in orders"
          :key="order.id"
          class="p-4 rounded-xl"
        >
          <div class="flex items-center justify-between mb-3">
            <span class="text-sm text-gray-500">订单号:{{ order.id }}</span>
            <span :class="['text-sm', statusMap[order.status]['color']]">{{ statusMap[order.status]['text'] }}</span>
          </div>

          <div v-for="(detail, idx) in order.details" :key="index" class="flex items-start space-x-4 mb-3">
            <img :src="detail.cover || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'" class="w-20 h-20 object-cover rounded-lg flex-shrink-0" :alt="detail.product_name">
            <div class="flex-1 min-w-0">
              <h3 class="text-base font-medium mb-1 truncate">{{ detail.product_name }}</h3>
              <p class="text-sm text-gray-500 mb-1">{{ order.note }}</p>
              <p class="text-sm text-gray-500">{{ order.pay_date && formatDate(order.pay_date) }}</p>
              <p class="text-sm text-gray-500">¥{{ detail.price }} x {{ detail.number }} 份</p>
            </div>
          </div>

          <div class="flex justify-between items-center pt-3 border-t border-gray-100">
            <div class="text-base font-medium text-green-600">¥{{ order.total_price }}</div>
            <div class="space-x-2">
              <button
                v-if="order.status === 'NOT_PAY'"
                class="px-4 py-1.5 text-sm text-white bg-green-600 rounded-full"
                @click="handlePay(order)"
              >
                立即支付
              </button>
              <button
                v-if="order.status === 'NOT_PAY'"
                class="px-4 py-1.5 text-sm text-gray-600 bg-gray-100 rounded-full"
                @click="handleCancel(order)"
              >
                取消支付
              </button>
              <button
                v-if="order.status === 'PAY'"
                class="px-4 py-1.5 text-sm text-gray-600 bg-gray-100 rounded-full"
                @click="handleViewDetail(order)"
              >
                查看详情
              </button>
            </div>
          </div>
        </FrostedGlass>
      </van-list>

      <!-- 无数据提示 -->
      <div v-if="!loading && orders.length === 0" class="flex flex-col items-center justify-center py-12">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
        </svg>
        <p class="mt-4 text-gray-500">暂无订单记录</p>
      </div>
    </div>
    <!-- 订单详情弹窗 -->
    <van-dialog
      v-model:show="showDetailDialog"
      title="订单详情"
      :show-cancel-button="false"
      confirm-button-text="确定"
      class="rounded-lg"
      confirm-button-color="#4caf50"
    >
      <div v-if="orderDetail" class="p-4 space-y-3">
        <div class="flex justify-between items-center">
          <span class="text-gray-500">订单号</span>
          <span class="font-medium">{{ orderDetail.id }}</span>
        </div>
        <div class="flex justify-between items-center">
          <span class="text-gray-500">订单状态</span>
          <span :class="['font-medium', statusMap[orderDetail.status].color]">{{ statusMap[orderDetail.status].text }}</span>
        </div>
        <div class="flex justify-between items-center">
          <span class="text-gray-500">支付方式</span>
          <span class="font-medium">{{ orderDetail.pay_type === 'Alipay' ? '支付宝' : '微信' }}</span>
        </div>
        <div class="flex justify-between items-center">
          <span class="text-gray-500">支付时间</span>
          <span class="font-medium">{{ orderDetail.pay_date && formatDate(orderDetail.pay_date) }}</span>
        </div>
        <div class="flex justify-between items-center">
          <span class="text-gray-500">订单金额</span>
          <span class="font-medium text-green-600">¥{{ orderDetail.total_price }}</span>
        </div>
        <div class="border-t border-gray-100 pt-3 mt-3">
          <div class="text-gray-500 mb-2">收货信息</div>
          <div class="space-y-2">
            <div class="flex justify-between items-center">
              <span class="text-gray-500">姓名</span>
              <span class="font-medium">{{ orderDetail.receive_name }}</span>
            </div>
            <div class="flex justify-between items-center">
              <span class="text-gray-500">手机</span>
              <span class="font-medium">{{ orderDetail.receive_phone }}</span>
            </div>
            <div class="flex justify-between items-center">
              <span class="text-gray-500">邮箱</span>
              <span class="font-medium">{{ orderDetail.receive_email }}</span>
            </div>
            <div class="flex justify-between items-center">
              <span class="text-gray-500">地址</span>
              <span class="font-medium">{{ orderDetail.receive_address }}</span>
            </div>
          </div>
        </div>
        <div v-if="orderDetail.note" class="border-t border-gray-100 pt-3">
          <div class="text-gray-500 mb-2">备注</div>
          <div class="text-sm">{{ orderDetail.note }}</div>
        </div>
      </div>
    </van-dialog>
  </AppLayout>
</template>

<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router';
import AppLayout from '@/components/layout/AppLayout.vue'
import FrostedGlass from '@/components/ui/FrostedGlass.vue'
import { useTitle } from '@vueuse/core';
import { showConfirmDialog, showToast, Dialog } from 'vant';
import { formatDate } from '@/utils/tools';

// 导入接口
import { getOrderListAPI, cancelOrderAPI, getOrderInfoAPI } from "@/api/order";

const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);

const router = useRouter()

const loading = ref(false)
const finished = ref(false)
const limit = ref(10)
const page = ref(0)
const orders = ref([])
const showDetailDialog = ref(false)
const orderDetail = ref(null)
// 订单状态映射
const statusMap = {
  NOT_PAY: { text: '待支付', color: 'text-orange-500' },
  PAY: { text: '已支付', color: 'text-green-500' },
  CANCEL: { text: '已取消', color: 'text-gray-500' },
  APPLY_REFUND: { text: '申请退款', color: 'text-red-500' },
  REFUND: { text: '已退款', color: 'text-red-500' },
  REFUND_ERROR: { text: '退款失败', color: 'text-red-500' },
}
// 模拟订单数据
// const orders = ref([
//   {
//     id: 1,
//     orderNo: 'ORDER202503210001',
//     status: 'pending',
//     statusText: '待支付',
//     statusColor: 'text-orange-500',
//     title: '亲子阅读课程',
//     description: '3-6岁儿童亲子阅读指导',
//     image: 'https://cdn.ipadbiz.cn/mlaj/images/jbwr0qZvpD4.jpg',
//     amount: 299,
//     createTime: '2025-03-21 10:30:00'
//   },
//   {
//     id: 2,
//     orderNo: 'ORDER202503210002',
//     status: 'paid',
//     statusText: '已支付',
//     statusColor: 'text-green-500',
//     title: '儿童绘画课程',
//     description: '儿童创意绘画启蒙课程',
//     image: 'https://cdn.ipadbiz.cn/mlaj/images/27kCu7bXGEI.jpg',
//     amount: 199,
//     createTime: '2025-03-21 09:15:00'
//   }
// ])

// 初始化加载订单列表
// 加载更多
const onLoad = async () => {
  const nextPage = page.value;
  const res = await getOrderListAPI({ limit: limit.value, page: nextPage, status: '' });
  if (res.code) {
    orders.value = [...orders.value, ...res.data];
    finished.value = res.data.length < limit.value;
    page.value = nextPage + 1;
  }
  loading.value = false;
};

// 支付订单
const handlePay = (order) => {
  router.push(`/checkout/${order.id}`)
}

// 查看订单详情
const handleViewDetail = async (order) => {
  try {
    const { code, data } = await getOrderInfoAPI({ i: order.id });
    if (code) {
      orderDetail.value = data;
      showDetailDialog.value = true;
    }
  } catch (error) {
    console.error('获取订单详情失败:', error);
    showToast('获取订单详情失败');
  }
};

// 取消订单
const handleCancel = async (order) => {
  showConfirmDialog({
    title: '温馨提示',
    message: '您是否确定要取消该订单?',
    confirmButtonColor: '#4caf50',
    })
      .then(async() => {
        try {
          const { code } = await cancelOrderAPI({ i: order.id });
          if (code) {
            const index = orders.value.findIndex((item) => item.id === order.id);
            // 更新订单状态为已取消
            orders.value[index].status = 'CANCEL';
            orders.value[index].statusText = '已取消';
            orders.value[index].statusColor = 'text-gray-500';
            // 显示提示信息
            showToast('订单已取消');
          }
        } catch (error) {
          console.error('取消订单失败:', error);
        }
      })
      .catch(() => {
        // on cancel
      });
};
</script>