|
@@ -0,0 +1,296 @@
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import {useEffect, useMemo, useState} from "react"
|
|
|
|
|
+import SubTitle from "@/components/subTitle"
|
|
|
|
|
+import ProductGrid from "@/components/products/ProductGridClient"
|
|
|
|
|
+import {useSearchParams} from "next/navigation";
|
|
|
|
|
+
|
|
|
|
|
+interface MenuItem {
|
|
|
|
|
+ key: string
|
|
|
|
|
+ label: string
|
|
|
|
|
+ children?: MenuItem[]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface ProductMenuProps {
|
|
|
|
|
+ menuItems: MenuItem[]
|
|
|
|
|
+ products: ProductItem[]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default function ProductMenu({ menuItems, products }: ProductMenuProps) {
|
|
|
|
|
+ // console.log(products);
|
|
|
|
|
+ const params = useSearchParams();
|
|
|
|
|
+ let baseSelect = '';
|
|
|
|
|
+ if(params.get('keyword')){
|
|
|
|
|
+ baseSelect = params.get('keyword') || '';
|
|
|
|
|
+ if(Object.is(params.get('keyword'),"软件产品")){
|
|
|
|
|
+ if (menuItems.filter((item) => Object.is(item.key, "软件产品")).length > 0) {
|
|
|
|
|
+ baseSelect = menuItems[0].children?.[0].key || '';
|
|
|
|
|
+ }
|
|
|
|
|
+ }else if(Object.is(params.get('keyword'),"硬件产品")){
|
|
|
|
|
+ if (menuItems.filter((item) => Object.is(item.key, "硬件产品")).length > 0) {
|
|
|
|
|
+ baseSelect = menuItems[1].children?.[0].key || '';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }else if(menuItems[0]?.children){
|
|
|
|
|
+ if(menuItems[0]?.children?.length>0){
|
|
|
|
|
+ baseSelect = menuItems[0].children?.[0].key || '';
|
|
|
|
|
+ }
|
|
|
|
|
+ }else {
|
|
|
|
|
+ baseSelect = menuItems[0]?.key || '';
|
|
|
|
|
+ }
|
|
|
|
|
+ const [selectedKey, setSelectedKey] = useState(baseSelect)
|
|
|
|
|
+ const [expandedKeys, setExpandedKeys] = useState<string[]>([])
|
|
|
|
|
+ const showProductList = useMemo(
|
|
|
|
|
+ () => products.filter((product) => product.productType === selectedKey),
|
|
|
|
|
+ [products, selectedKey]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if(window.innerWidth > 640){
|
|
|
|
|
+ for (const item of menuItems) {
|
|
|
|
|
+ if (item.children) {
|
|
|
|
|
+ const child = item.children.find((child) => child.key === baseSelect)
|
|
|
|
|
+ if (child) {
|
|
|
|
|
+ // console.log(child)
|
|
|
|
|
+ setExpandedKeys([item.key])
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [menuItems, baseSelect])
|
|
|
|
|
+
|
|
|
|
|
+ const findLabelByKey = (key: string): string => {
|
|
|
|
|
+ for (const item of menuItems) {
|
|
|
|
|
+ if (item.key === key) {
|
|
|
|
|
+ return item.label
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (item.children) {
|
|
|
|
|
+ const child = item.children.find((child) => child.key === key)
|
|
|
|
|
+ if (child) {
|
|
|
|
|
+ return child.label
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return key
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleMenuClick = (key: string) => {
|
|
|
|
|
+ setSelectedKey(key)
|
|
|
|
|
+ const urlSearchParams = new URLSearchParams(params.toString());
|
|
|
|
|
+ urlSearchParams.set('keyword', key);
|
|
|
|
|
+ window.history.replaceState({}, '', `?${urlSearchParams.toString()}`);
|
|
|
|
|
+
|
|
|
|
|
+ // 查找包含这个子菜单的父菜单并自动展开
|
|
|
|
|
+ for (const item of menuItems) {
|
|
|
|
|
+ if (item.children) {
|
|
|
|
|
+ const child = item.children.find((child) => child.key === key)
|
|
|
|
|
+ if (child) {
|
|
|
|
|
+ // 如果找到子菜单,自动展开父菜单
|
|
|
|
|
+ setExpandedKeys((prev) => {
|
|
|
|
|
+ if (prev.includes(item.key)) {
|
|
|
|
|
+ return prev
|
|
|
|
|
+ }
|
|
|
|
|
+ return [...prev, item.key]
|
|
|
|
|
+ })
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const toggleExpanded = (key: string) => {
|
|
|
|
|
+ setExpandedKeys((prev) => (prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <div className="sm:hidden">
|
|
|
|
|
+ <MobileMenu
|
|
|
|
|
+ menuItems={menuItems}
|
|
|
|
|
+ selectedKey={selectedKey}
|
|
|
|
|
+ expandedKeys={expandedKeys}
|
|
|
|
|
+ onMenuClick={handleMenuClick}
|
|
|
|
|
+ onToggleExpanded={toggleExpanded}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="flex mt-6 sm:mt-10 mb-12 sm:mb-16 w-full sm:w-4/5 mx-auto px-4 sm:px-0">
|
|
|
|
|
+ <div className="hidden sm:block w-56 lg:w-64 p-4">
|
|
|
|
|
+ <DesktopMenu
|
|
|
|
|
+ menuItems={menuItems}
|
|
|
|
|
+ selectedKey={selectedKey}
|
|
|
|
|
+ expandedKeys={expandedKeys}
|
|
|
|
|
+ onMenuClick={handleMenuClick}
|
|
|
|
|
+ onToggleExpanded={toggleExpanded}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="flex-1 p-2 sm:p-4 lg:p-8 sm:pt-8">
|
|
|
|
|
+ <div className="sm:hidden mt-3 mb-10">
|
|
|
|
|
+ <SubTitle title={findLabelByKey(selectedKey)} customStyle={{ textAlign: "center" }} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <ProductGrid products={showProductList} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function DesktopMenu({
|
|
|
|
|
+ menuItems,
|
|
|
|
|
+ selectedKey,
|
|
|
|
|
+ expandedKeys,
|
|
|
|
|
+ onMenuClick,
|
|
|
|
|
+ onToggleExpanded,
|
|
|
|
|
+ }: {
|
|
|
|
|
+ menuItems: MenuItem[]
|
|
|
|
|
+ selectedKey: string
|
|
|
|
|
+ expandedKeys: string[]
|
|
|
|
|
+ onMenuClick: (key: string) => void
|
|
|
|
|
+ onToggleExpanded: (key: string) => void
|
|
|
|
|
+}) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="space-y-1">
|
|
|
|
|
+ {menuItems.map((item) => (
|
|
|
|
|
+ <div key={item.key}>
|
|
|
|
|
+ <div
|
|
|
|
|
+ className={`flex items-center justify-between px-4 py-6 text-sm font-medium cursor-pointer rounded-lg transition-all duration-300 ${
|
|
|
|
|
+ selectedKey === item.key ||
|
|
|
|
|
+ (item.children && item.children.some((child) => selectedKey === child.key))
|
|
|
|
|
+ ? "bg-blue-500 text-white shadow-lg"
|
|
|
|
|
+ : "text-gray-700 bg-blue-100 hover:bg-gray-100 hover:text-gray-900"
|
|
|
|
|
+ }`}
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ if (item.children) {
|
|
|
|
|
+ onToggleExpanded(item.key)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ onMenuClick(item.key)
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <span className="transition-all duration-200">{item.label}</span>
|
|
|
|
|
+ {item.children && (
|
|
|
|
|
+ <svg
|
|
|
|
|
+ className={`w-4 h-4 transition-all duration-300 ease-in-out ${
|
|
|
|
|
+ expandedKeys.includes(item.key) ? "rotate-90" : ""
|
|
|
|
|
+ } ${
|
|
|
|
|
+ selectedKey === item.key ||
|
|
|
|
|
+ (item.children && item.children.some((child) => selectedKey === child.key))
|
|
|
|
|
+ ? "text-white"
|
|
|
|
|
+ : "text-gray-500 hover:text-blue-400"
|
|
|
|
|
+ }`}
|
|
|
|
|
+ fill="none"
|
|
|
|
|
+ stroke="currentColor"
|
|
|
|
|
+ viewBox="0 0 24 24"
|
|
|
|
|
+ >
|
|
|
|
|
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {item.children && (
|
|
|
|
|
+ <div
|
|
|
|
|
+ className={`ml-4 mt-1 space-y-1 overflow-hidden transition-all duration-300 ease-in-out ${
|
|
|
|
|
+ expandedKeys.includes(item.key) ? "max-h-96 opacity-100" : "max-h-0 opacity-0"
|
|
|
|
|
+ }`}
|
|
|
|
|
+ >
|
|
|
|
|
+ {item.children.map((child) => (
|
|
|
|
|
+ <div
|
|
|
|
|
+ key={child.key}
|
|
|
|
|
+ className={`px-4 py-4 text-sm cursor-pointer rounded-lg transition-all duration-200 ${
|
|
|
|
|
+ selectedKey === child.key
|
|
|
|
|
+ ? "bg-blue-500 text-white shadow-md"
|
|
|
|
|
+ : "text-gray-600 hover:bg-blue-50 hover:text-blue-700"
|
|
|
|
|
+ }`}
|
|
|
|
|
+ onClick={() => onMenuClick(child.key)}
|
|
|
|
|
+ >
|
|
|
|
|
+ <span className="flex items-center">{child.label}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function MobileMenu({
|
|
|
|
|
+ menuItems,
|
|
|
|
|
+ selectedKey,
|
|
|
|
|
+ expandedKeys,
|
|
|
|
|
+ onMenuClick,
|
|
|
|
|
+ onToggleExpanded,
|
|
|
|
|
+ }: {
|
|
|
|
|
+ menuItems: MenuItem[]
|
|
|
|
|
+ selectedKey: string
|
|
|
|
|
+ expandedKeys: string[]
|
|
|
|
|
+ onMenuClick: (key: string) => void
|
|
|
|
|
+ onToggleExpanded: (key: string) => void
|
|
|
|
|
+}) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="bg-white shadow-sm">
|
|
|
|
|
+ <div className="px-4 py-3">
|
|
|
|
|
+ <div className="flex flex-wrap gap-2">
|
|
|
|
|
+ {menuItems.map((item) => (
|
|
|
|
|
+ <div key={item.key} className="relative">
|
|
|
|
|
+ <button
|
|
|
|
|
+ className={`px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 ${
|
|
|
|
|
+ selectedKey === item.key ||
|
|
|
|
|
+ (item.children && item.children.some((child) => selectedKey === child.key))
|
|
|
|
|
+ ? "bg-blue-500 text-white shadow-md"
|
|
|
|
|
+ : "bg-gray-100 text-gray-700 hover:bg-gray-200"
|
|
|
|
|
+ }`}
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ if (item.children) {
|
|
|
|
|
+ onToggleExpanded(item.key)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ onMenuClick(item.key)
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <span className="flex items-center gap-1">
|
|
|
|
|
+ {item.label}
|
|
|
|
|
+ {item.children && (
|
|
|
|
|
+ <svg
|
|
|
|
|
+ className={`w-3 h-3 transition-transform duration-200 ${
|
|
|
|
|
+ expandedKeys.includes(item.key) ? "rotate-180" : ""
|
|
|
|
|
+ }`}
|
|
|
|
|
+ fill="none"
|
|
|
|
|
+ stroke="currentColor"
|
|
|
|
|
+ viewBox="0 0 24 24"
|
|
|
|
|
+ >
|
|
|
|
|
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+
|
|
|
|
|
+ {item.children && expandedKeys.includes(item.key) && (
|
|
|
|
|
+ <div className="absolute top-full left-0 mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-10 min-w-[160px]">
|
|
|
|
|
+ {item.children.map((child) => (
|
|
|
|
|
+ <button
|
|
|
|
|
+ key={child.key}
|
|
|
|
|
+ className={`w-full text-left px-4 py-3 text-sm transition-colors duration-200 first:rounded-t-lg last:rounded-b-lg ${
|
|
|
|
|
+ selectedKey === child.key ? "bg-blue-500 text-white" : "text-gray-700 hover:bg-gray-50"
|
|
|
|
|
+ }`}
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ onMenuClick(child.key)
|
|
|
|
|
+ onToggleExpanded(item.key) // Close dropdown after selection
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {child.label}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|