Sfoglia il codice sorgente

个人中心订单详情页

zhangzf 6 ore fa
parent
commit
7380f921cb

+ 34 - 51
src/app/(public)/customer/(has-dashboard)/order/view/order_id/[id]/_components/OrderAddressCard.tsx

@@ -1,18 +1,15 @@
-import type {
-  AddressInfo,
-  OrderData,
-} from "@/types/api/customer/order/vieworder";
+import type { CustomerOrder } from "@/types/customer/accountOrderDetail/order";
+
+// 提取地址类型
+type OrderAddressItem = CustomerOrder["addresses"]["edges"][number]["node"];
 
 interface Props {
-  orderData: OrderData;
+  orderData: CustomerOrder;
 }
 
-const getFullAddress = (
-  address: AddressInfo
-) => {
+const getFullAddress = (address: OrderAddressItem) => {
   return [
-    address.address1,
-    address.address2,
+    address.address,
     address.city,
     address.state,
     address.postcode,
@@ -22,11 +19,7 @@ const getFullAddress = (
     .join(", ");
 };
 
-function AddressBlock({
-  address,
-}: {
-  address: AddressInfo;
-}) {
+function AddressBlock({ address }: { address: OrderAddressItem }) {
   return (
     <div
       className="
@@ -43,16 +36,10 @@ function AddressBlock({
           text-[14px]
         "
       >
-        {address.first_name}{" "}
-        {address.last_name}
-
-        <span className="mx-2 text-[#999]">
-          |
-        </span>
-
+        {address.firstName} {address.lastName}
+        <span className="mx-2 text-[#999]">|</span>
         {address.phone}
       </div>
-
       {/* address */}
       <div
         className="
@@ -78,7 +65,6 @@ function AddressBlock({
             stroke="currentColor"
             strokeWidth="1.5"
           />
-
           <circle
             cx="12"
             cy="9.5"
@@ -87,18 +73,22 @@ function AddressBlock({
             strokeWidth="1.5"
           />
         </svg>
-
-        <span>
-          {getFullAddress(address)}
-        </span>
+        <span>{getFullAddress(address)}</span>
       </div>
     </div>
   );
 }
 
-export default function OrderAddressCard({
-  orderData,
-}: Props) {
+export default function OrderAddressCard({ orderData }: Props) {
+  // 从edges里筛选收货地址、账单地址
+  const addressNodes = orderData.addresses.edges.map((edge) => edge.node);
+  const shippingAddress = addressNodes.find(
+    (item) => item.addressType === "order_shipping",
+  );
+  const billingAddress = addressNodes.find(
+    (item) => item.addressType === "order_billing",
+  );
+
   return (
     <section
       className="
@@ -110,25 +100,18 @@ export default function OrderAddressCard({
         shadow-[0_2px_12px_rgba(0,0,0,0.06)]
       "
     >
-      <div>
-        <h2 className="text-[17px]">
-          Ship to:
-        </h2>
-
-        <AddressBlock
-          address={orderData.shipping_address}
-        />
-      </div>
-
-      <div className="mt-6">
-        <h2 className="text-[17px]">
-          Billing to:
-        </h2>
-
-        <AddressBlock
-          address={orderData.billing_address}
-        />
-      </div>
+      {shippingAddress && (
+        <div>
+          <h2 className="text-[17px]">Ship to:</h2>
+          <AddressBlock address={shippingAddress} />
+        </div>
+      )}
+      {billingAddress && (
+        <div className="mt-6">
+          <h2 className="text-[17px]">Billing to:</h2>
+          <AddressBlock address={billingAddress} />
+        </div>
+      )}
     </section>
   );
-}
+}

+ 90 - 24
src/app/(public)/customer/(has-dashboard)/order/view/order_id/[id]/_components/OrderInfoBlock.tsx

@@ -1,18 +1,24 @@
-import type { OrderData } from "@/types/api/customer/order/vieworder";
-
+import type { CustomerOrder } from "@/types/customer/accountOrderDetail/order";
+import Image from "next/image";
 interface Props {
-  orderData: OrderData;
+  orderData: CustomerOrder;
 }
-
 export default function PromotionDetails({ orderData }: Props) {
   const money = (value: number) =>
     new Intl.NumberFormat("en-US", {
       style: "currency",
-      currency: orderData.order_currency_code,
+      currency: "USD",
     }).format(value);
 
-  const discount = Math.abs(orderData.discount_amount);
-
+  // 各个优惠金额,取绝对值用于展示
+  const couponDiscount = Math.abs(orderData.discountAmount ?? 0);
+  const giftCardAmt = Math.abs(orderData.giftcardAmount ?? 0);
+  const rewardPointsAmt = Math.abs(orderData.rewardPointsAmount ?? 0);
+  const vipPlusAmt = orderData.vipPlusAmount
+    ? Number(orderData.vipPlusAmount)
+    : 0;
+  const vipDiscountAmt = Math.abs(orderData.vipDiscountAmount ?? 0);
+  const earnedPoints = orderData.rewardPointsEarned ?? 0;
   return (
     <section
       className="
@@ -25,40 +31,94 @@ export default function PromotionDetails({ orderData }: Props) {
       "
     >
       <h2 className="text-[18px]">Promotion Details</h2>
-
       <div className="mt-6 border-t border-[#EEEEEE] pt-4">
         {/* Subtotal */}
         <div className="flex justify-between py-3 text-[14px]">
           <span>Subtotal</span>
-
-          <span>{money(orderData.sub_total)}</span>
+          <span>{money(orderData.subTotal)}</span>
         </div>
 
-        {/* Discount */}
-        <div className="flex justify-between py-3 text-[14px]">
-          <span>Discount</span>
+        {/* Gift Card */}
+        {giftCardAmt > 0 && (
+          <div className="flex justify-between py-3 text-[14px]">
+            <span>Gift Card</span>
+            <span className="bg-[#FFD600] px-2 py-1">
+              -{money(giftCardAmt)}
+            </span>
+          </div>
+        )}
 
-          <span className={discount > 0 ? "bg-[#FFD600] px-2 py-1" : ""}>
-            {discount > 0 ? `-${money(discount)}` : money(0)}
-          </span>
-        </div>
+        {/* Coupon Discount */}
+        {couponDiscount > 0 && (
+          <div className="flex justify-between py-3 text-[14px]">
+            <span>
+              Discount
+              {orderData.couponCode ? `(Code:${orderData.couponCode})` : ""}
+            </span>
+            <span className="bg-[#FFD600] px-2 py-1">
+              -{money(couponDiscount)}
+            </span>
+          </div>
+        )}
+
+        {/* Reward Points */}
+        {rewardPointsAmt > 0 && (
+          <div className="flex justify-between py-3 text-[14px]">
+            <span>Points</span>
+            <span className="bg-[#FFD600] px-2 py-1">
+              -{money(rewardPointsAmt)}
+            </span>
+          </div>
+        )}
+
+        {/* VIP Plus Discount - 使用图片代替css标签 */}
+        {vipDiscountAmt > 0 && (
+          <div className="flex justify-between py-3 text-[14px]">
+            <span className="relative">
+              <Image
+                src="/image/account/plus-tag.png"
+                alt="PLUS"
+                width={60}
+                height={20}
+                className="inline-block align-middle"
+              />
+            </span>
+            <span className="bg-[#FFD600] px-2 py-1">
+              -{money(vipDiscountAmt)}
+            </span>
+          </div>
+        )}
+
+        {/* WIGGINS PLUS */}
+        {vipPlusAmt > 0 && (
+          <div className="flex justify-between py-3 text-[14px]">
+            <span>ASTERIA PLUS</span>
+            <span>{money(vipPlusAmt)}</span>
+          </div>
+        )}
 
         {/* Shipping */}
         <div className="flex justify-between py-3 text-[14px]">
           <span>Shipping</span>
-
           <span>
-            {orderData.shipping_amount === 0
+            {orderData.shippingAmount === 0
               ? "Free"
-              : money(orderData.shipping_amount)}
+              : money(orderData.shippingAmount)}
           </span>
         </div>
 
+        {/* Insurance for Lost */}
+        {Number(orderData.shippingInsuranceAmount) > 0 && (
+          <div className="flex justify-between py-3 text-[14px]">
+            <span>Insurance for Lost(3%)</span>
+            <span>{money(Number(orderData.shippingInsuranceAmount))}</span>
+          </div>
+        )}
+
         {/* Tax */}
         <div className="flex justify-between py-3 text-[14px]">
           <span>Tax</span>
-
-          <span>{money(orderData.tax_amount)}</span>
+          <span>{money(orderData.taxAmount)}</span>
         </div>
 
         {/* Total */}
@@ -74,9 +134,15 @@ export default function PromotionDetails({ orderData }: Props) {
           "
         >
           <span>Total:</span>
-
-          <span className="font-medium">{money(orderData.grand_total)}</span>
+          <span className="font-medium">{money(orderData.grandTotal)}</span>
         </div>
+
+        {/* You've earned points 底部文字 */}
+        {earnedPoints > 0 && (
+          <div className="mt-4 text-[14px] text-[#8A8A8A]">
+            You've earned {earnedPoints} points
+          </div>
+        )}
       </div>
     </section>
   );

+ 68 - 88
src/app/(public)/customer/(has-dashboard)/order/view/order_id/[id]/_components/ProductList.tsx

@@ -1,37 +1,30 @@
-import type { OrderData } from "@/types/api/customer/order/vieworder";
-
+// ProductList.tsx
+import type { CustomerOrder } from "@/types/customer/accountOrderDetail/order";
 interface Props {
-  orderData: OrderData;
+  orderData: CustomerOrder;
 }
-
 const formatDate = (date: string) => {
   if (!date) return "";
-
-  const datePart = date.split(" ")[0];
+  const datePart = date.split("T")[0]; // 原来split(" ")会报错!ISO时间是 2026-09-30T01:14:14-07:00,没有空格
   const [year, month, day] = datePart.split("-");
-
   return `${Number(month)}/${Number(day)}/${year}`;
 };
-
-const formatMoney = (
-  value: number,
-  currency: string
-) => {
+const formatMoney = (value: number, currency: string) => {
   return new Intl.NumberFormat("en-US", {
     style: "currency",
     currency,
   }).format(value);
 };
-
 const formatStatus = (status: string) => {
   return status
     .replace(/_/g, " ")
     .replace(/\b\w/g, (char) => char.toUpperCase());
 };
+export default function ProductList({ orderData }: Props) {
+  // 从edges取出商品数组
+  console.log('orderData------------------',orderData);
+  const itemList = orderData.items.edges.map((edge) => edge.node);
 
-export default function ProductList({
-  orderData,
-}: Props) {
   return (
     <section
       className="
@@ -45,12 +38,8 @@ export default function ProductList({
       {/* status */}
       <div className="flex items-center gap-2 text-[16px]">
         <span className="h-2 w-2 rounded-full bg-black" />
-
-        <span>
-          {formatStatus(orderData.status)}
-        </span>
+        <span>{formatStatus(orderData.status)}</span>
       </div>
-
       {/* Order / Date */}
       <div
         className="
@@ -65,65 +54,64 @@ export default function ProductList({
           text-[#8A8A8A]
         "
       >
-        <span>
-          Order #{orderData.increment_id}
-        </span>
-
-        <span>
-          Date: {formatDate(orderData.created_at)}
-        </span>
+        <span>Order #{orderData.incrementId}</span>
+        <span>Date: {formatDate(orderData.createdAt)}</span>
       </div>
-
       {/* Products */}
       <div>
-        {orderData.items.map((item, index) => (
-          <div
-            key={item.id}
-            className={`
-              flex
-              gap-4
-              py-4
-              ${
-                index !== orderData.items.length - 1
-                  ? "border-b border-[#EEEEEE]"
-                  : ""
-              }
-            `}
-          >
-            {/* 商品图:
-                当前接口没有 image,所以做兼容
-            */}
+        {itemList.map((item, index) => {
+          // 解析baseImage
+          let imgUrl = "";
+          try {
+            const imgJson = JSON.parse(item.baseImage);
+            imgUrl = imgJson.small_image_url;
+          } catch (err) {
+            imgUrl = "";
+          }
+          return (
             <div
-              className="
-                h-[174px]
-                w-[130px]
-                shrink-0
-                overflow-hidden
-                bg-[#F5F5F5]
-              "
+              key={item.id}
+              className={`
+                flex
+                gap-4
+                py-4
+                ${
+                  index !== itemList.length - 1
+                    ? "border-b border-[#EEEEEE]"
+                    : ""
+                }
+              `}
             >
-              {item.image ? (
-                <img
-                  src={item.image}
-                  alt={item.name}
-                  className="h-full w-full object-cover"
-                />
-              ) : null}
-            </div>
-
-            <div className="flex min-w-0 flex-1 flex-col">
-              <h3
+              {/* 商品图 */}
+              <div
                 className="
-                  text-[14px]
-                  leading-[20px]
-                  text-black
+                  h-[174px]
+                  w-[130px]
+                  shrink-0
+                  overflow-hidden
+                  bg-[#F5F5F5]
                 "
               >
-                {item.name}
-              </h3>
-
-              {/* 当前 API 没有 options,有了以后直接显示 */}
-              {item.options && (
+                {imgUrl ? (
+                  <img
+                    src={imgUrl}
+                    alt={item.name}
+                    className="h-full w-full object-cover"
+                  />
+                ) : null}
+              </div>
+              <div className="flex min-w-0 flex-1 flex-col">
+                <h3
+                  className="
+                    text-[14px]
+                    leading-[20px]
+                    text-black
+                  "
+                >
+                  {item.name}
+                </h3>
+                 {/* 当前 API 没有 options,有了以后直接显示 */}
+              {item.sku && (
                 <div
                   className="
                     mt-3
@@ -134,26 +122,18 @@ export default function ProductList({
                     text-[#333]
                   "
                 >
-                  {item.options}
+                  {item.sku}
                 </div>
-              )}
-
+              )}                
               <div className="mt-auto text-[15px]">
-                <span>
-                  {formatMoney(
-                    item.price,
-                    orderData.order_currency_code
-                  )}
-                </span>
-
-                <span className="ml-1">
-                  x {item.qty_ordered}
-                </span>
+                  <span>{formatMoney(item.price, "USD")}</span>
+                  <span className="ml-1">x {item.qtyOrdered}</span>
+                </div>
               </div>
             </div>
-          </div>
-        ))}
+          );
+        })}
       </div>
     </section>
   );
-}
+}

+ 28 - 14
src/app/(public)/customer/(has-dashboard)/order/view/order_id/[id]/page.tsx

@@ -3,8 +3,13 @@ import OrderInfoBlock from "./_components/OrderInfoBlock";
 import ProductList from "./_components/ProductList";
 import OrderAddressCard from "./_components/OrderAddressCard";
 // import PriceSummary from "./_components/PriceSummary";
-import {restApiFetch} from "@utils/bagisto/index";
-import type {  OrderData,OrderApiResponse } from "@/types/api/customer/order/vieworder";
+// import {restApiFetch} from "@utils/bagisto/index";
+import { serverGraphqlFetch } from "@/utils/bagisto/index";
+import {
+GET_ORDER_DETAIL_BY_ID
+} from "@/graphql";
+import type { CustomerOrderResponse } from "@/types/customer/accountOrderDetail/order";
+// import type {  OrderData,OrderApiResponse } from "@/types/api/customer/order/vieworder";
 type Params = {
   id: string;
 };
@@ -16,15 +21,24 @@ export default async function OrderDetail({ params }: { params: Params }) {
   console.log("orderId:", orderId);
 
   // 2. 服务端 fetch 请求我们自己的 Next.js API
-  const res = await restApiFetch<OrderApiResponse>({
-    api:`/customer/orders/${orderId}`,
-    method:"GET",
-    isRoute: false
-  }
-  );
-
-  const orderData: OrderData = res.body.data;
-  console.log("父组件 orderData:", orderData, !!orderData); 
+  // const res = await restApiFetch<OrderApiResponse>({
+  //   api:`/customer/orders/${orderId}`,
+  //   method:"GET",
+  //   isRoute: false
+  // }
+  // );
+  const { data: orderDetailData } = await serverGraphqlFetch<
+    CustomerOrderResponse,
+    { id: number }
+  >({
+    query: GET_ORDER_DETAIL_BY_ID,
+    variables: {
+      id: Number(orderId),
+    },
+  });
+  console.log("父组件 orderDetailData:", orderDetailData.customerOrder); 
+  // const orderData: OrderData = res.body.data;
+  // console.log("父组件 orderData:", orderData, !!orderData); 
   return (
     <div className="min-h-screen bg-white">
       {/* 顶部固定导航栏(客户端组件:返回按钮) */}
@@ -32,13 +46,13 @@ export default async function OrderDetail({ params }: { params: Params }) {
 
       {/* 商品列表:循环渲染,自动兼容1件或多件商品 */}
           <ProductList
-        orderData={orderData}
+        orderData={orderDetailData.customerOrder}
       />
 
       <div className="">
         {/* 收货信息、支付信息区块 */}
-        <OrderInfoBlock orderData={orderData} />
-         <OrderAddressCard orderData={orderData} />
+        <OrderInfoBlock orderData={orderDetailData.customerOrder} />
+         <OrderAddressCard orderData={orderDetailData.customerOrder}/>
         {/* 分享按钮区域 */}
         <div className="my-6">
           <h3 className="text-lg font-bold mb-4">Share order:</h3>

+ 71 - 0
src/graphql/catalog/queries/GetOrderDetailById.ts

@@ -0,0 +1,71 @@
+import { gql, type TypedDocumentNode } from "@apollo/client";
+import type { CustomerOrderResponse } from "@/types/customer/accountOrderDetail/order";
+
+export const GET_ORDER_DETAIL_BY_ID: TypedDocumentNode<CustomerOrderResponse> = gql`
+query GetCustomerOrder($id: ID!) {
+  customerOrder(id: $id) {
+    id
+    incrementId
+    status
+    createdAt
+    customerFullName
+    subTotal
+    grandTotal
+    discountAmount
+    couponCode
+    giftcardNumber
+    giftcardAmount
+    rewardPointsUsed
+    rewardPointsAmount
+    rewardPointsEarned
+    vipPlusAmount
+    vipDiscountAmount
+    shippingAmount
+    shippingTitle
+    shippingInsuranceAmount
+    taxAmount
+    totalItemCount
+    totalQtyOrdered
+    items {
+      edges {
+        node {
+          id
+          sku
+          name
+          qtyOrdered
+          price
+          total
+          type
+          baseImage
+        }
+      }
+    }
+    addresses {
+      edges {
+        node {
+          addressType
+          firstName
+          lastName
+          phone
+          address
+          city
+          state
+          country
+          postcode
+        }
+      }
+    }
+    payment {
+      method
+      methodTitle
+    }
+    shipments {
+      edges {
+        node {
+          id
+        }
+      }
+    }
+  }
+}
+`;

+ 1 - 0
src/graphql/catalog/queries/index.ts

@@ -13,3 +13,4 @@ export { GET_CATEHORY_BY_ID } from "./GetCategoryById";
 export { GET_PRODUCT_BY_ID } from "./GetProductById";
 export { GET_CATEGORY_ATTR_FILTERS } from "./CategoryAttributeFilters";
 export { CATEGORY_PRODUCTS } from "./GetCategoryProducts";
+export { GET_ORDER_DETAIL_BY_ID } from "./GetOrderDetailById";

+ 94 - 0
src/types/customer/accountOrderDetail/order.ts

@@ -0,0 +1,94 @@
+// @/types/order/customer-order.ts
+export type CustomerOrderResponse = {
+  data: {
+    customerOrder: CustomerOrder;
+  };
+};
+
+export type CustomerOrder = {
+  id: string;
+  incrementId: string;
+  status: string;
+  createdAt: string;
+  customerFullName: string;
+  subTotal: number;
+  grandTotal: number;
+  discountAmount: number;
+  couponCode: string | null;
+  giftcardNumber: string | null;
+  giftcardAmount: number | null;
+  rewardPointsUsed: number;
+  rewardPointsAmount: number;
+  rewardPointsEarned: number;
+  vipPlusAmount: string | null;
+  vipDiscountAmount: string | null;
+  shippingAmount: number;
+  shippingTitle: string;
+  shippingInsuranceAmount: string;
+  taxAmount: number;
+  totalItemCount: number;
+  totalQtyOrdered: number;
+  items: OrderItemConnection;
+  addresses: OrderAddressConnection;
+  payment: OrderPayment;
+  shipments: OrderShipmentConnection;
+};
+
+// 商品列表
+export type OrderItemConnection = {
+  edges: Array<{
+    node: OrderItem;
+  }>;
+};
+
+export type OrderItem = {
+  id: string;
+  sku: string;
+  name: string;
+  qtyOrdered: number;
+  price: number;
+  total: number;
+  type: string;
+  baseImage: string; // 这里是JSON字符串,后续需要JSON.parse解析
+};
+
+// 地址
+export type OrderAddressConnection = {
+  edges: Array<{
+    node: OrderAddress;
+  }>;
+};
+
+export type OrderAddress = {
+  addressType: string;
+  firstName: string;
+  lastName: string;
+  phone: string;
+  address: string;
+  city: string;
+  state: string;
+  country: string;
+  postcode: string;
+};
+
+// 支付信息
+export type OrderPayment = {
+  method: string;
+  methodTitle: string;
+};
+
+// 发货信息
+export type OrderShipmentConnection = {
+  edges: Array<{
+    node: {
+      id: string;
+    };
+  }>;
+};
+
+export type BaseImageRaw = {
+  small_image_url: string;
+  medium_image_url: string;
+  large_image_url: string;
+  original_image_url: string;
+};