| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- "use client";
- import { useState, useEffect, useRef } from "react";
- import { useSearchProduct } from "@/utils/hooks/useSearchProduct";
- import Grid from "@/components/theme/ui/grid/Grid";
- // import dynamicImport from "next/dynamic";
- import HotSearch from "@/app/(public)/search/_components/HotSearch"
- import { useMediaQuery } from "@utils/hooks/useMediaQueryHook";
- import Link from "next/link";
- // import { usePathname } from "next/navigation";
- // const ProductGridItems = dynamicImport(
- // () => import("@/components/catalog/product/ProductGridItems"),
- // );
- import NewProductGridItems from "@/components/catalog/product/NewProductGridItems";
- // 定义弹窗入参类型
- interface SearchPopProps {
- open: boolean;
- onClose: () => void;
- }
- export default function SearchPop({ open, onClose }: SearchPopProps) {
- const { data, error, searchProducts } = useSearchProduct();
- const [keyword, setKeyword] = useState("");
- const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
- const products = data?.edges?.map((e) => e.node) || [];
- const isDesktop = useMediaQuery("(min-width: 1024px)");
- // const pathname = usePathname();
- // useEffect(() => {
- // // 打开状态下,路由变化就关闭弹窗
- // if (open) {
- // handleClose();
- // }
- // }, [pathname]);
- // 执行搜索接口
- const triggerSearch = async (searchWord: string) => {
- if (!searchWord.trim()) return;
- const params = {
- suggest: true,
- query: searchWord,
- first: 8,
- };
- console.log("【接口入参 variables】", params);
- const res = await searchProducts(params);
- console.log("【接口完整返回结果】", res);
- console.log("【商品列表数据】", res.data?.search);
- };
- // 防抖输入
- const handleKeywordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- const value = e.target.value;
- setKeyword(value);
- if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
- debounceTimerRef.current = setTimeout(() => triggerSearch(value), 300);
- };
- // 点击搜索/回车搜索
- const handleSearch = () => {
- if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
- triggerSearch(keyword);
- };
- // 销毁清除定时器
- useEffect(() => {
- return () => {
- if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
- };
- }, []);
- // 轮播占位文案
- const placeholderList = [
- "Search for products...",
- "Find best sellers",
- "Search new arrivals",
- "Search discount items",
- "Search trending products",
- ];
- const [placeholderIndex, setPlaceholderIndex] = useState(0);
- useEffect(() => {
- const interval = setInterval(() => {
- setPlaceholderIndex((prev) => (prev + 1) % placeholderList.length);
- }, 3000);
- return () => clearInterval(interval);
- }, [placeholderList.length]);
- const currentPlaceholder = placeholderList[placeholderIndex];
- // 关闭弹窗并清空关键词
- const handleClose = () => {
- setKeyword("");
- onClose();
- };
- // 弹窗未打开直接返回null
- if (!open) return null;
- return (
- // 全屏弹窗根容器,纯div无多余大括号
- <div className="fixed inset-0 z-[9999] bg-white overflow-y-auto">
- {/* 顶部搜索栏区域 */}
- <div className="relative flex items-center justify-center px-4 py-4 border-b border-neutral-200 pt-10">
- {/* 右上角关闭按钮 严格24px w-6 h-6 */}
- <button
- onClick={handleClose}
- className="absolute right-4 top-4 w-6 h-6 flex items-center justify-center cursor-pointer"
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth={1.8}
- stroke="currentColor"
- className="w-full h-full text-neutral-700"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M6 18L18 6M6 6l12 12"
- />
- </svg>
- </button>
- </div>
- {/* 搜索输入框容器 */}
- <div className="block w-full justify-center md:hidden px-4 mt-3 z-10">
- <div
- className={`${isDesktop ? "max-w-[550px]" : ""} relative w-full mx-auto xl:min-w-[516px] outline-none hover:outline-none`}
- >
- <input
- onChange={handleKeywordChange}
- onKeyDown={(e) => e.key === "Enter" && handleSearch()}
- autoComplete="off"
- className="input w-full rounded-lg border border-neutral-200 bg-white py-2 pl-3 pr-10 text-sm text-black outline-none placeholder:text-neutral-500 focus:ring-2 focus:ring-neutral-300 dark:border-neutral-800 dark:bg-transparent dark:text-white dark:placeholder:text-neutral-400 md:pl-4"
- name="search"
- placeholder={currentPlaceholder}
- type="text"
- value={keyword}
- />
- {/* 搜索图标按钮 */}
- <div
- onClick={handleSearch}
- className="absolute bottom-0 right-1 top-0 flex w-9 cursor-pointer items-center justify-center border-l border-neutral-200 dark:border-neutral-700 md:border-0"
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth="1.5"
- className="size-5 stroke-neutral-500 dark:stroke-white md:stroke-black"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
- />
- </svg>
- </div>
- </div>
- </div>
- {/* hot search */}
- <HotSearch />
- {/* 商品列表内容区 */}
- <div >
- {error && <p className="text-red-500 my-2 text-center">{error}</p>}
- {data?.edges?.length === 0 && keyword && (
- <p className="text-center text-gray-500 my-6">No matching products</p>
- )}
- {keyword && data?.edges?.length !== 0 && (
- <div>
- <Grid className="grid grid-flow-row grid-cols-2 gap-5 lg:gap-11.5 w-full max-w-screen-2xl mx-auto md:grid-cols-3 lg:grid-cols-4 px-4 xss:px-7.5">
- <NewProductGridItems products={products} />
- </Grid>
- <div className="flex justify-center mt-10 mb-16">
- <Link
- href={`/search?q=${encodeURIComponent(keyword)}`}
- onClick={handleClose}
- className="inline-block border border-black rounded-full px-12 py-4 text-xl font-normal text-black hover:bg-gray-100 transition-colors"
- >
- View More
- </Link>
- </div>
- </div>
- )}
- </div>
- </div>
- );
- }
|