Explorar el Código

产品分类页调整

zhangzf hace 2 días
padre
commit
0bbe753acd

+ 5 - 2
src/app/(public)/category/[slug]/[id]/_components/ProductListing.tsx

@@ -13,6 +13,7 @@ import { useRouter, usePathname } from "next/navigation";
 import {  useApolloClient } from "@apollo/client/react";
 import type{ CategoryProductsResult,PageInfo,CategoryProductNode,CategoryAttrFilterItem } from "@components/catalog/type";
 import {CATEGORY_PRODUCTS} from "@/graphql";
+import {transformFiltersForApi} from "@utils/helper";
 interface QueryState {
   // search: string;
 
@@ -65,6 +66,8 @@ export default function ProductListing({
 
   const fetchProducts = async (currentQuery: QueryState) => {
     try {
+      // 转换
+    const finalFilterObj = transformFiltersForApi(currentQuery.filters);
       const res = await apolloClient.query<CategoryProductsResult>({
         query: CATEGORY_PRODUCTS,
         variables: 
@@ -80,14 +83,14 @@ export default function ProductListing({
         
           sortKey: currentQuery.sortKey,
           reverse: currentQuery.reverse,
-          filter:JSON.stringify( currentQuery.filters),
+          filter:JSON.stringify(finalFilterObj),
 
           first: 15,
 
           after: null,
         },
       });
-      console.log("res----------------------------------:",res,currentQuery);
+      console.log("res----------------------------------:",res,currentQuery,finalFilterObj);
       
       // const res = await fetch("/api/products", {
       //   method: "POST",

+ 14 - 4
src/app/(public)/category/[slug]/[id]/page.tsx

@@ -3,6 +3,7 @@ import { notFound } from "next/navigation";
 // import { isArray } from "@/utils/type-guards";
 // import FilterList from "@components/theme/filters/FilterList";
 // import Pagination from "@components/catalog/Pagination";
+import Image from "next/image";
 import ProductListing from "./_components/ProductListing";
 import type{
   // ProductsResponse,
@@ -33,6 +34,7 @@ import {
   // extractNumericId,
   // findCategoryBySlug,
   newBuildProductFilters,
+  transformFiltersForApi,
 } from "@utils/helper";
 import { serverGraphqlFetch } from "@/utils/bagisto/index";
 import type {
@@ -175,8 +177,8 @@ export default async function CategoryPage({
   if (numericId) {
     filterObject.category_id = numericId;
   }
-
-  const filterInput = JSON.stringify(filterObject);
+  const finalFilterObj =transformFiltersForApi(filterObject);
+  const filterInput = JSON.stringify(finalFilterObj);
   // 默认获取产品数据接口:
   const { data: categoryProductDatas } = await serverGraphqlFetch<
     CategoryProductsResult,
@@ -306,10 +308,18 @@ export default async function CategoryPage({
               name: translation?.name ?? "",
             }}
           /> */}
-          <CategoryDesc
+           <div className="flex justify-center">
+              <Image
+               src={categoryProductDatas? categoryProductDatas.categoryProducts.banner: ''}
+               alt={'categoryBanner'}
+               width={358}
+               height={176}
+              />
+          </div>
+           <CategoryDesc
             title={translation?.name ?? ""}
             description={
-              "Alipearl Hair provides many style best quality but cheap lace wigs,including lace front wigs, lace closure wigs, full lace human hair wigs, HD transparent lace wigs, 13x4 frontal wigs, 13x6 lace front wigs, 360 lace wigs, all textures available straight, body wave, deep wave, water wave, curly hair wigs, all lengths from 8 inch to 40 inch, different hair colors natural black, brown, blonde, highlight, ginger, burgundy etc."
+              categoryProductDatas? categoryProductDatas.categoryProducts.description: ''
             } //translation?.description ?? ""}
           />
         </Suspense>

+ 3 - 0
src/components/catalog/type.ts

@@ -176,6 +176,7 @@ export interface CategoryAttrFilterItem  {
         adminName: string;
         sortOrder: number;
         swatchValue: string | null;
+        productCount:number;
       };
     }>;
   };
@@ -210,6 +211,8 @@ export type CategoryProductNode = {
 export type CategoryProductsResult = {
   categoryProducts: {
     totalCount: number;
+    description: string;
+    banner: string;
     pageInfo:{
       endCursor:string,
       hasNextPage: boolean

+ 36 - 38
src/components/theme/filters/NewMobileFilter.tsx

@@ -71,29 +71,29 @@ export default function MobileFilter({
     console.log("【抽屉是否打开isOpen】", isOpen);
   }, [isOpen, filters]);
 
-  /**
-   * 单选
-   */
-  const toggleOption = (code: string, id: string) => {
-    setTempFilters((prev) => {
-      const current = prev[code];
-
-      if (current === id) {
-        const next = { ...prev };
-
-        delete next[code];
-        // 打印点击后最新的临时筛选
-        console.log("【点击选项后临时筛选tempFilters】", next);
-        return next;
-      }
-
-      return {
-        ...prev,
+ const toggleOption = (code: string, id: string) => {
+  setTempFilters((prev) => {
+    const currentStr = prev[code] ?? "";
+    const selectedSet = new Set(currentStr ? currentStr.split(",") : []);
+    // debugger
+    if (selectedSet.has(id)) {
+      // 已选中 → 删除
+      selectedSet.delete(id);
+    } else {
+      // 未选中 → 添加
+      selectedSet.add(id);
+    }
 
-        [code]: id,
-      };
-    });
-  };
+    const next = { ...prev };
+    if (selectedSet.size === 0) {
+      delete next[code];
+    } else {
+      next[code] = Array.from(selectedSet).join(",");
+    }
+    console.log("【点击选项后临时筛选tempFilters】", next);
+    return next;
+  });
+};
 
   const toggleGroupExpand = (code: string) => {
     setExpandedGroups((prev) => ({
@@ -175,39 +175,37 @@ export default function MobileFilter({
                           <FilterPrice
                             priceAttr={attr.node}
                             initialRange={{
-                              minPrice: tempFilters.minPrice
-                                ? Number(tempFilters.minPrice)
+                              minPrice: tempFilters.price_from
+                                ? Number(tempFilters.price_from)
                                 : undefined,
-                              maxPrice: tempFilters.maxPrice
-                                ? Number(tempFilters.maxPrice)
+                              maxPrice: tempFilters.price_to
+                                ? Number(tempFilters.price_to)
                                 : undefined,
                             }}
                             onChangeComplete={(range) => {
                               setTempFilters((prev) => ({
                                 ...prev,
-                                minPrice: String(range.minPrice),
-                                maxPrice: String(range.maxPrice),
+                                price_from: String(range.minPrice),
+                                price_to: String(range.maxPrice),
                               }));
                             }}
                           />
                         ) : (
-                          (attr.node.options.edges || []).map((opt: any) => {
-                            const checked =
-                              tempFilters[attr.node.code] == opt.node._id;
-                            return (
+                         (attr.node.options.edges || []).map((opt: any) => {
+                            const selectedStr = tempFilters[attr.node.code] ?? "";
+                            const idStr = String(opt.node._id);
+                            const checked = selectedStr.split(",").includes(idStr);
+                            return opt.node.productCount > 0 && (
                               <label
                                 key={opt.node._id}
-                                className="flex gap-3 text-lg"
+                                className="flex gap-3 text-lg items-center"
                               >
                                 <input
                                   type="checkbox"
                                   checked={checked}
-                                  onChange={() =>
-                                    toggleOption(attr.node.code, opt.node._id)
-                                  }
+                                  onChange={() => toggleOption(attr.node.code, String(opt.node._id))}
                                 />
-
-                                <span>{opt.node.adminName}</span>
+                                <span className="wrap-break-word">{opt.node.adminName}</span>
                               </label>
                             );
                           })

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

@@ -25,6 +25,7 @@ export const GET_CATEGORY_ATTR_FILTERS = gql`
                 adminName
                 sortOrder
                 swatchValue
+                productCount
               }
             }
           }

+ 2 - 0
src/graphql/catalog/queries/GetCategoryProducts.ts

@@ -4,6 +4,8 @@ export const CATEGORY_PRODUCTS = gql`
 query CategoryProducts($slug: String!, $filter: String, $sortKey: String, $reverse: Boolean, $first: Int, $after: String) {
   categoryProducts(slug: $slug, filter: $filter, sortKey: $sortKey, reverse: $reverse, first: $first, after: $after) {
     totalCount
+    description
+    banner
     pageInfo {
      endCursor     
      hasNextPage   

+ 33 - 6
src/utils/helper.ts

@@ -300,7 +300,6 @@ export function buildProductFilters(params: {
 export function newBuildProductFilters(params: {
   [key: string]: string | string[] | undefined;
 }) {
-  // ==========黑名单:这些字段不会筛选==========
   const EXCLUDE_KEYS = new Set([
     "q",
     "sort",
@@ -309,13 +308,13 @@ export function newBuildProductFilters(params: {
     "after",
     "page",
   ]);
+  const PRICE_KEYS = new Set(["price_from", "price_to"]);
 
   const extractId = (value: string) => {
     if (/^\d+$/.test(value)) return value;
     const match = value.match(/\/(\d+)$/);
     return match ? match[1] : null;
   };
-
   const parseParamIds = (raw: string | string[] | undefined) => {
     let list: string[] = [];
     if (typeof raw === "string") {
@@ -328,13 +327,20 @@ export function newBuildProductFilters(params: {
       .filter((id): id is string => Boolean(id));
   };
 
-  const filterObject: Record<string, string> = {};
+  const filterObject: Record<string, string > = {};
 
-  // 遍历全部传入参数
   for (const [key, value] of Object.entries(params)) {
-    // 黑名单字段直接跳过
     if (EXCLUDE_KEYS.has(key)) continue;
 
+    // ✅ 价格字段单独处理,不执行ID提取
+    if (PRICE_KEYS.has(key)) {
+      if (typeof value === "string" && value) {
+        filterObject[key] = value;
+      }
+      continue;
+    }
+
+    // 普通属性筛选,沿用原来的ID提取逻辑
     const ids = parseParamIds(value);
     if (ids.length > 0) {
       filterObject[key] = ids.join(",");
@@ -362,4 +368,25 @@ export function getUuId() {
         localStorage.setItem(key, id);
     }
     return id;
-};
+};
+// 筛选字段要多个match {\"wig_color\":{\"match\":\"33\"},\"price_from\":10,\"price_to\":212}
+ export function transformFiltersForApi(raw: Record<string, any>) {
+   // filter传参需要调整
+  const result: Record<string, any> = {};
+
+  for (const key in raw) {
+    const val = raw[key];
+    // 价格字段特殊处理:转数字,直接赋值,不包match
+    if (key === "price_from" || key === "price_to") {
+      // 空/undefined 跳过
+      if (val === undefined || val === null || val === "") continue;
+      result[key] = Number(val);
+    } else {
+      // 其他所有筛选属性:套上 {match:xxx}
+      result[key] = {
+        match:String(val),
+      };
+    }
+   }
+   return result;
+  }