|
@@ -1,286 +1,49 @@
|
|
|
-"use client"
|
|
|
|
|
-
|
|
|
|
|
-import { useState } from "react"
|
|
|
|
|
|
|
+import Image from "next/image"
|
|
|
import MainTitle from "@/components/MainTitle"
|
|
import MainTitle from "@/components/MainTitle"
|
|
|
-import SubTitle from "@/components/subTitle";
|
|
|
|
|
-import Image from "next/image";
|
|
|
|
|
-
|
|
|
|
|
-export default function ProductShowcase() {
|
|
|
|
|
- const [selectedKey, setSelectedKey] = useState("software")
|
|
|
|
|
- const [expandedKeys, setExpandedKeys] = useState<string[]>([])
|
|
|
|
|
-
|
|
|
|
|
- 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;
|
|
|
|
|
- };
|
|
|
|
|
|
|
+import ProductMenu from "@/components/products/ProductMenuClient"
|
|
|
|
|
+import {serverGet} from "@/utils/request";
|
|
|
|
|
+import AnimatedSection from "@/components/AnimatedSection";
|
|
|
|
|
+import {Suspense} from "react";
|
|
|
|
|
|
|
|
- const products = [
|
|
|
|
|
- {
|
|
|
|
|
- id: 1,
|
|
|
|
|
- title: "可视化数字孪生平台",
|
|
|
|
|
- bgColor: "bg-gradient-to-br from-blue-900 via-blue-800 to-slate-800",
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 2,
|
|
|
|
|
- title: "水库雨水情测报与大坝安全监测平台",
|
|
|
|
|
- bgColor: "bg-gradient-to-br from-teal-900 via-emerald-800 to-cyan-800",
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 3,
|
|
|
|
|
- title: "物联网平台",
|
|
|
|
|
- bgColor: "bg-gradient-to-br from-teal-900 via-emerald-800 to-cyan-800",
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 4,
|
|
|
|
|
- title: "智慧档案库可视化平台",
|
|
|
|
|
- bgColor: "bg-gradient-to-br from-blue-900 via-indigo-800 to-slate-800",
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 5,
|
|
|
|
|
- title: "智慧环保监测管理平台",
|
|
|
|
|
- bgColor: "bg-gradient-to-br from-slate-900 via-blue-900 to-indigo-900",
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 6,
|
|
|
|
|
- title: "自然灾害应急能力提升平台",
|
|
|
|
|
- bgColor: "bg-gradient-to-br from-slate-900 via-gray-800 to-blue-900",
|
|
|
|
|
- },
|
|
|
|
|
- ]
|
|
|
|
|
|
|
+export default async function ProductShowcase() {
|
|
|
|
|
|
|
|
- const menuItems = [
|
|
|
|
|
- {
|
|
|
|
|
- key: "software",
|
|
|
|
|
- label: "软件产品",
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- key: "hardware",
|
|
|
|
|
- label: "硬件产品",
|
|
|
|
|
- children: [
|
|
|
|
|
- {
|
|
|
|
|
- key: "sensor",
|
|
|
|
|
- label: "传感设备",
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- key: "iot",
|
|
|
|
|
- label: "物联设备",
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- key: "terminal",
|
|
|
|
|
- label: "智能交互终端",
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
|
|
+ const res = await serverGet<ProductCategory[]>("/webSite/getProductCategoryAndType",null,{
|
|
|
|
|
+ next: {
|
|
|
|
|
+ revalidate: 180
|
|
|
},
|
|
},
|
|
|
- ]
|
|
|
|
|
-
|
|
|
|
|
- const handleMenuClick = (key: string) => {
|
|
|
|
|
- setSelectedKey(key)
|
|
|
|
|
|
|
+ cache: "force-cache"
|
|
|
|
|
+ })
|
|
|
|
|
+ if(res.code !== 200){
|
|
|
|
|
+ return <div>服务器错误</div>
|
|
|
}
|
|
}
|
|
|
|
|
+ const menuItems = res.data.map( (item) => ({
|
|
|
|
|
+ key: item.productCategoryName,
|
|
|
|
|
+ label: item.productCategoryName,
|
|
|
|
|
+ children: item.productTypes.map( (type) => ({
|
|
|
|
|
+ key: type.productTypeName,
|
|
|
|
|
+ label: type.productTypeName,
|
|
|
|
|
+ }))
|
|
|
|
|
+ }))
|
|
|
|
|
|
|
|
- const toggleExpanded = (key: string) => {
|
|
|
|
|
- setExpandedKeys((prev) => (prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]))
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const DesktopMenu = () => (
|
|
|
|
|
- <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
|
|
|
|
|
- ? "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) {
|
|
|
|
|
- toggleExpanded(item.key)
|
|
|
|
|
- } else {
|
|
|
|
|
- handleMenuClick(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 ? "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={() => handleMenuClick(child.key)}
|
|
|
|
|
- >
|
|
|
|
|
- <span className="flex items-center">{child.label}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- ))}
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
- </div>
|
|
|
|
|
- ))}
|
|
|
|
|
- </div>
|
|
|
|
|
- )
|
|
|
|
|
|
|
|
|
|
- const MobileMenu = () => (
|
|
|
|
|
- <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) {
|
|
|
|
|
- toggleExpanded(item.key)
|
|
|
|
|
- } else {
|
|
|
|
|
- handleMenuClick(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={() => {
|
|
|
|
|
- handleMenuClick(child.key)
|
|
|
|
|
- setExpandedKeys([]) // Close dropdown after selection
|
|
|
|
|
- }}
|
|
|
|
|
- >
|
|
|
|
|
- {child.label}
|
|
|
|
|
- </button>
|
|
|
|
|
- ))}
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
- </div>
|
|
|
|
|
- ))}
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ const products = res.data.flatMap( (item) => item.productTypes.flatMap( (type) => type.productCenters))
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<>
|
|
<>
|
|
|
- <div>
|
|
|
|
|
- <Image src={"/assets/productions/1.png"} alt={"产品中心"} width={1920} height={1080} />
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className="py-6 sm:py-10">
|
|
|
|
|
- <MainTitle title={"产品中心"} />
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className="sm:hidden">
|
|
|
|
|
- <MobileMenu />
|
|
|
|
|
- </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 />
|
|
|
|
|
|
|
+ <AnimatedSection effect="slide" direction="left">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <Image src={"/assets/productions/1.png"} alt={"产品中心"} width={1920} height={1080}/>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ </AnimatedSection>
|
|
|
|
|
|
|
|
- <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>
|
|
|
|
|
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
|
|
|
|
|
- {products.map((product) => (
|
|
|
|
|
- <div
|
|
|
|
|
- key={product.id}
|
|
|
|
|
- className="bg-white rounded-lg overflow-hidden shadow-lg hover:shadow-xl transition-all duration-300 hover:scale-[1.02]"
|
|
|
|
|
- >
|
|
|
|
|
- <div className={`h-32 sm:h-40 lg:h-48 ${product.bgColor} relative overflow-hidden`}>
|
|
|
|
|
- <div className="absolute inset-3 sm:inset-4 space-y-2">
|
|
|
|
|
- <div className="flex space-x-2">
|
|
|
|
|
- <div className="w-12 sm:w-16 h-1.5 sm:h-2 bg-white/20 rounded"></div>
|
|
|
|
|
- <div className="w-8 sm:w-12 h-1.5 sm:h-2 bg-white/20 rounded"></div>
|
|
|
|
|
- <div className="w-14 sm:w-20 h-1.5 sm:h-2 bg-white/20 rounded"></div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className="grid grid-cols-2 gap-2 mt-3 sm:mt-4">
|
|
|
|
|
- <div className="h-12 sm:h-16 bg-white/10 rounded border border-white/20"></div>
|
|
|
|
|
- <div className="h-12 sm:h-16 bg-white/10 rounded border border-white/20"></div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className="mt-3 sm:mt-4 space-y-1">
|
|
|
|
|
- <div className="w-full h-0.5 sm:h-1 bg-white/20 rounded"></div>
|
|
|
|
|
- <div className="w-3/4 h-0.5 sm:h-1 bg-white/20 rounded"></div>
|
|
|
|
|
- <div className="w-1/2 h-0.5 sm:h-1 bg-white/20 rounded"></div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className="absolute top-1 sm:top-2 right-1 sm:right-2 w-6 sm:w-8 h-6 sm:h-8 border border-white/30 rounded-full"></div>
|
|
|
|
|
- <div className="absolute bottom-1 sm:bottom-2 left-1 sm:left-2 w-3 sm:w-4 h-3 sm:h-4 bg-cyan-400/60 rounded"></div>
|
|
|
|
|
- <div className="absolute bottom-1 sm:bottom-2 right-1 sm:right-2 w-4 sm:w-6 h-4 sm:h-6 bg-blue-400/60 rounded"></div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className="p-4 sm:p-6 text-center">
|
|
|
|
|
- <h3 className="text-sm sm:text-base lg:text-lg font-medium text-gray-900 mb-3 sm:mb-4 leading-tight min-h-[2.5rem] sm:min-h-[3rem] flex items-center justify-center">
|
|
|
|
|
- {product.title}
|
|
|
|
|
- </h3>
|
|
|
|
|
- <button className="bg-blue-500 hover:bg-blue-600 text-white px-4 sm:px-6 py-2 sm:py-2.5 rounded-lg font-medium transition-all duration-200 text-sm sm:text-base hover:scale-105 active:scale-95">
|
|
|
|
|
- 了解详情
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- ))}
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div className="py-6 sm:py-10">
|
|
|
|
|
+ <MainTitle title={"产品中心"}/>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <AnimatedSection effect="slide" direction="right">
|
|
|
|
|
+ <Suspense fallback={<div>加载中...</div>}>
|
|
|
|
|
+ <ProductMenu menuItems={menuItems} products={products} />
|
|
|
|
|
+ </Suspense>
|
|
|
|
|
+ </AnimatedSection>
|
|
|
</>
|
|
</>
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|