Explorar o código

refactor(products): 重构核心产品组件和软件产品展示逻辑

- 使用 useMemo 优化产品过滤和排序性能
- 添加软件产品分类过滤功能
- 将静态图片展示改为动态轮播图组件
- 实现响应式布局适配移动端和桌面端
- 优化产品数据处理和渲染逻辑
- 添加加载状态和空数据提示
- 统一产品展示卡片样式和交互效果
nahida hai 4 meses
pai
achega
9b5f14dac9
Modificáronse 3 ficheiros con 95 adicións e 85 borrados
  1. 6 1
      src/app/page.tsx
  2. 12 15
      src/components/CoreProducts.tsx
  3. 77 69
      src/components/ProductionSoft.tsx

+ 6 - 1
src/app/page.tsx

@@ -95,10 +95,15 @@ export default async function Home() {
         <CoreProducts products={productList}/>
       </AnimatedSection>
 
+      <AnimatedSection effect="fade">
+        <div className='my-6 sm:my-10'>
+          <MainTitle title="软件产品" titleLetter="SOFTWARE_PRODUCTS"/>
+        </div>
+      </AnimatedSection>
 
       {/*软件产品模块*/}
       <AnimatedSection effect="slide" direction="left">
-        <ProductionSoft softIntroduction={basicInfo.softwareIntroduction || ""}/>
+        <ProductionSoft products={productList} />
       </AnimatedSection>
       {/* 知识产权统计模块 */}
       <AnimatedSection effect="scale">

+ 12 - 15
src/components/CoreProducts.tsx

@@ -11,12 +11,18 @@ export default function CoreProducts({products}: ProductMenuProps) {
 
   const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL as string
 
-  products = products.reverse()
+  const filteredProducts = useMemo(() => {
+    return (products ?? []).filter((item) => item.productCategoryName !== "软件产品")
+  }, [products])
+
+  const orderedProducts = useMemo(() => {
+    return [...filteredProducts].reverse()
+  }, [filteredProducts])
 
   const tabs = useMemo(() => {
-    const allTypes = (products ?? []).flatMap((category) => category.productTypes ?? [])
+    const allTypes = (orderedProducts ?? []).flatMap((category) => category.productTypes ?? [])
     return Array.from(new Set(allTypes.map((type) => type.productTypeName)))
-  }, [products])
+  }, [orderedProducts])
 
   const [activeTab, setActiveTab] = useState(() => tabs[0] ?? "")
 
@@ -44,25 +50,16 @@ export default function CoreProducts({products}: ProductMenuProps) {
 
   const showProducts = useMemo(() => {
     if (!activeTab) return []
-    const allTypes = (products ?? []).flatMap((category) => category.productTypes ?? [])
+    const allTypes = (orderedProducts ?? []).flatMap((category) => category.productTypes ?? [])
     const matched = allTypes
       .filter((type) => type.productTypeName === activeTab)
       .flatMap((type) => type.productCenters ?? [])
 
     return matched.slice(0, 4)
-  }, [products, activeTab])
+  }, [orderedProducts, activeTab])
 
   return (
-    <section className="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 min-h-150">
-      <div className="flex justify-end mb-5 sm:mb-8">
-        <Link
-          href="/products"
-          className="sm:text-2xl text-xl shuheiti text-blue-700 hover:text-blue-800 inline-flex items-center gap-1 transition-colors"
-        >
-          更多
-          <span className="sm:text-2xl text-xl leading-none" aria-hidden="true">→</span>
-        </Link>
-      </div>
+    <section className="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 min-h-180">
 
       <div className="w-full border border-gray-300 rounded-md overflow-hidden">
         <div className="flex overflow-x-auto">

+ 77 - 69
src/components/ProductionSoft.tsx

@@ -1,78 +1,86 @@
-import React from 'react';
-import SubTitle from "@/components/subTitle";
+'use client'
+
+import React, {useEffect, useMemo, useState} from 'react';
 import Image from "next/image";
 import Link from 'next/link';
+import {Carousel} from "antd";
+
+function ProductionSoft({products}: {products: ProductCategory[]}) {
+  const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || ''
+  const [isMobile, setIsMobile] = useState(false);
+
+  useEffect(() => {
+    const checkMobile = () => setIsMobile(window.innerWidth < 640);
+    checkMobile();
+    window.addEventListener("resize", checkMobile);
+    return () => window.removeEventListener("resize", checkMobile);
+  }, []);
+
+  const softwareCategory = useMemo(() => {
+    return (products ?? []).find((item) => item.productCategoryName === "软件产品")
+  }, [products])
+
+  const softwareProducts = useMemo(() => {
+    return softwareCategory?.productTypes?.[0]?.productCenters ?? []
+  }, [softwareCategory])
+
+  const chunkSize = isMobile ? 1 : 3
+  const slides = useMemo(() => {
+    if (softwareProducts.length === 0) return []
+    return softwareProducts.reduce((acc, item, index) => {
+      if (index % chunkSize === 0) acc.push([])
+      acc[acc.length - 1].push(item)
+      return acc
+    }, [] as ProductItem[][])
+  }, [softwareProducts, chunkSize])
 
-function ProductionSoft({softIntroduction}: {softIntroduction: string}) {
   return (
-    <div className={"w-full mx-auto sm:w-4/5"}>
-      <div className="hidden lg:block">
-        <SubTitle title={"软件产品"}/>
-      </div>
-      <div className="hidden lg:grid grid-cols-1 lg:grid-cols-5 gap-6 h-[600px] mt-10">
-        <div
-            className="lg:col-span-2 bg-[url('/assets/home/14.png')] bg-cover from-blue-900 to-blue-700 rounded-lg p-10 text-white flex flex-col justify-between">
-          <div>
-            <h2 className="text-2xl font-bold mt-10 border-b-2 border-blue-400 pb-2 inline-block">软件简介</h2>
-            <p className="text-white text-2xl leading-relaxed mb-8 mt-10 tracking-widest font-medium">
-              {softIntroduction}
-            </p>
-          </div>
-          <Link
-              href={"/products?keyword=软件产品"}
-              className="bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-lg font-medium transition-colors w-fit">
-            了解更多
-          </Link>
-        </div>
-        {/* PC端软件产品图片 */}
-        <div className="lg:col-span-3 grid grid-cols-2 gap-4">
-          <div className="col-span-2 rounded-lg overflow-hidden relative h-[300px]">
-            <Image
-                src="/assets/home/15.jpg"
-                fill
-                alt="软件产品图片"
-                sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
-            />
-          </div>
-          <div className="rounded-lg overflow-hidden relative h-[300px]">
-            <Image
-                src="/assets/home/16.jpg"
-                fill
-                alt="软件产品图片"
-                sizes="(max-width: 768px) 100vw, 50vw"
-            />
-          </div>
-          <div className="rounded-lg overflow-hidden relative h-[300px]">
-            <Image
-                src="/assets/home/17.jpg"
-                fill
-                alt="软件产品图片"
-                sizes="(max-width: 768px) 100vw, 50vw"
-            />
-          </div>
-        </div>
-      </div>
-      {/* 移动端软件产品图片 */}
-      <div className="lg:hidden mt-2">
-        <div
-            className="bg-[url('/assets/home/14.png')] bg-cover from-blue-900 to-blue-700 p-10 text-white flex flex-col justify-between">
-          <div>
-            <h2 className="text-1xl font-bold mt-10 border-b-2 border-blue-400 pb-2 inline-block">软件简介111</h2>
-            <p className="text-white text-1xl leading-relaxed mt-10 tracking-widest font-medium">
-              软件产品包括:物联网智慧园区综合平台、物联网智慧园区综合平台、自然灾害应急能力提升平台、物联网环保监测管理平台、水库雨水情测报与大坝安全监测平台等等......
-            </p>
-          </div>
-          <Image className="mt-6" src={"/assets/home/16.jpg"} width={1000} height={1000} alt={"软件产品"}/>
-          <Image className="mt-6" src={"/assets/home/17.jpg"} width={1000} height={1000} alt={"软件产品"}/>
-          <Link
-              href={"/products?keyword=软件产品"}
-              className="mt-6 bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-lg font-medium transition-colors w-fit">
-            了解更多
-          </Link>
-        </div>
+    <div className="w-full mx-auto sm:w-4/5">
+      <div className="mt-6">
+        {slides.length === 0 ? (
+          <div className="text-center text-gray-500 py-10">暂无软件产品数据</div>
+        ) : (
+          <Carousel arrows draggable dots autoplay autoplaySpeed={5000}>
+            {slides.map((group, groupIndex) => (
+              <div key={groupIndex}>
+                <div className={`grid ${isMobile ? "grid-cols-1" : "grid-cols-3"} gap-4 sm:gap-6 p-2 sm:p-4`}>
+                  {group.map((product) => (
+                    <Link
+                      key={product.productId}
+                      href={`/products/${product.productId}`}
+                      className="group bg-white rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-shadow duration-300 overflow-hidden"
+                    >
+                      <div className="w-full bg-gray-50 overflow-hidden">
+                        <Image
+                          src={product.productUrl ? BASE_URL + product.productUrl : "/assets/productions/2.png"}
+                          alt={product.productName}
+                          width={1000}
+                          height={1000}
+                          sizes={isMobile ? "90vw" : "30vw"}
+                          className="w-full h-auto object-contain transition-transform duration-300 group-hover:scale-105"
+                        />
+                      </div>
+                      <div className="p-4 sm:p-5 text-center">
+                        <div className="shuheiti text-base sm:text-lg text-gray-900 line-clamp-1">
+                          {product.productName}
+                        </div>
+                        <div className="mt-1 text-xs sm:text-sm text-gray-500 line-clamp-2">
+                          {product.productIntroduction}
+                        </div>
+                        <div className="mt-2 text-xs sm:text-sm text-gray-600 line-clamp-1">
+                          {product.productModel}
+                        </div>
+                      </div>
+                    </Link>
+                  ))}
+                </div>
+              </div>
+            ))}
+          </Carousel>
+        )}
       </div>
     </div>
   );
 }
 
-export default ProductionSoft;
+export default ProductionSoft;