浏览代码

feat(products): 实现核心产品组件的标签页切换和自动轮播功能

- 在 CoreProducts 组件中添加了标签页切换功能,支持按产品类型分类展示
- 实现了自动轮播效果,每5秒自动切换到下一个标签页
- 重构了产品数据处理逻辑,支持按产品分类和类型进行筛选展示
- 优化了产品卡片的布局样式,调整了图片缩放和文字大小
- 从主页添加了荣誉资质模块的展示
- 更新了知识产权统计模块的样式间距
- 调整了主页产品数据的获取和传递方式
- 从类型定义中移除了 ProductItem 接口的 isCore 字段
- 将 TypeScript 编译目标从 ES5 升级到 ES2017
nahida 4 月之前
父节点
当前提交
1c092805ca
共有 5 个文件被更改,包括 177 次插入166 次删除
  1. 106 153
      src/app/page.tsx
  2. 69 8
      src/components/CoreProducts.tsx
  3. 1 3
      src/components/IntellectualPropertyStats.tsx
  4. 0 1
      src/types/index.ts
  5. 1 1
      tsconfig.json

+ 106 - 153
src/app/page.tsx

@@ -8,162 +8,115 @@ import {IntellectualPropertyStats} from "@/components/IntellectualPropertyStats"
 import ServerClient from "@/components/serverClient";
 import {serverGet} from "@/utils/request";
 import AnimatedSection from "@/components/AnimatedSection";
+import Honor from "@/components/about/Honor";
 
 const features: Feature[] = [
-    {img: "/assets/home/8.png", title: "产品中心", subtitle: "PRODUCT_CENTER", href: "/products"},
-    {img: "/assets/home/9.png", title: "解决方案", subtitle: "SOLUTION", href: "/solutions"},
-    {img: "/assets/home/10.png", title: "新闻动态", subtitle: "NEWS", href: "/news"},
-    {img: "/assets/home/11.png", title: "服务支持", subtitle: "SERVICE_SUPPORT", href: "/support"},
-    {img: "/assets/home/12.png", title: "关于我们", subtitle: "ABOUT_US", href: "/about"},
+  {img: "/assets/home/8.png", title: "产品中心", subtitle: "PRODUCT_CENTER", href: "/products"},
+  {img: "/assets/home/9.png", title: "解决方案", subtitle: "SOLUTION", href: "/solutions"},
+  {img: "/assets/home/10.png", title: "新闻动态", subtitle: "NEWS", href: "/news"},
+  {img: "/assets/home/11.png", title: "服务支持", subtitle: "SERVICE_SUPPORT", href: "/support"},
+  {img: "/assets/home/12.png", title: "关于我们", subtitle: "ABOUT_US", href: "/about"},
 ]
 
-interface HardwareProduct {
-    id: string;
-    productCategoryName: string; // 产品大类名称(如:硬件产品/软件产品)
-    productTypes: ProductType[]; // 产品类型列表(如:[感应设备, 检测设备, 数据采集])
-    productTypeName: string;
-    isCore: string;
-    productId: string; // 实际数据中的“productId”(原接口写的“id”)
-    productName: string;
-    productCategory: string;
-    productType: string;
-    productUrl: string | null;
-    productIntroduction: string;
-    productModel: string;
-}
-
 export default async function Home() {
-    const basicInfoRes = await serverGet<BasicInfo[]>("/webSite/getBasicInfo", {
-        next: {
-            revalidate: 1800
-        },
-        cache: "force-cache"
-    })
-
-    const res = await serverGet<ProductCategory[]>("/webSite/getProductCategoryAndType", null, {
-        next: {
-            revalidate: 180
-        },
-        cache: "force-cache"
-    })
-    if (res.code !== 200) {
-        return <div>服务器错误</div>
-    }
-
-    // 优化后的筛选函数:硬件产品中 isCore=1 的数据,最多提取4条
-    const filterHardwareCoreProducts = (
-        data: ProductCategory[],
-        maxCount: number = 4
-    ): HardwareProduct[] => {
-        // 边界处理:非数组返回空数组
-        if (!Array.isArray(data)) return [];
-
-        const coreProducts = [];
-        // 第一层:遍历产品大类(软件产品/硬件产品)
-        for (const category of data) {
-            // 只处理硬件产品分类,且未收集满maxCount条时继续
-            if (
-                category.productCategoryName !== "硬件产品" ||
-                !Array.isArray(category.productTypes) ||
-                coreProducts.length >= maxCount
-            ) {
-                continue;
-            }
-
-            // 第二层:遍历硬件产品下的类型(感应设备/检测设备/数据采集)
-            for (const type of category.productTypes) {
-                if (!Array.isArray(type.productCenters) || coreProducts.length >= maxCount) {
-                    continue;
-                }
-
-                // 第三层:筛选当前类型下 isCore=1 的产品,且控制数量
-                for (const item of type.productCenters) {
-                    if (item.isCore === "1" && coreProducts.length < maxCount) {
-                        coreProducts.push({
-                            ...item,
-                            id: item.productId, // 补全id(映射productId)
-                            productCategoryName: category.productCategoryName, // 补全产品大类名称
-                            productTypes: category.productTypes, // 补全产品类型列表
-                            productTypeName: type.productTypeName,
-                        });
-                    }
-                    // 达到数量上限,立即终止当前层循环
-                    if (coreProducts.length >= maxCount) {
-                        break;
-                    }
-                }
-            }
-        }
-
-        return coreProducts;
-    };
-
-    const coreHardwareProducts = filterHardwareCoreProducts(res.data);
-    console.log("硬件核心产品:", coreHardwareProducts);
-
-    const basicInfo = basicInfoRes.data?.[0] || {
-        address: "",
-        companyProfile: "",
-        companyProfileUrl: "",
-        consultationHotline: "",
-        email: "",
-        hardwareIntroduction: "",
-        id: "",
-        qrCodeUrl: "",
-        serviceHotline: "",
-        softwareIntroduction: "",
-        telephone: ""
-    };
-    return (
-        <>
-            <AnimatedSection effect="fade">
-                <BannerCarousel/>
-            </AnimatedSection>
-
-
-            <AnimatedSection effect="slide" direction="left">
-                <div className="bg-[url('/assets/home/4.png')] w-full">
-                    <div className="custom-scrollbar overflow-x-auto">
-                        <div
-                            className="flex space-x-5 sm:space-x-6 md:space-x-8 min-w-max px-5 sm:px-6 md:px-10 mx-auto w-max">
-                            {features.map((f, idx) => (
-                                <FeatureCard key={idx} {...f} />
-                            ))}
-                        </div>
-                    </div>
-                </div>
-            </AnimatedSection>
-
-            {/*<AnimatedSection effect="fade">*/}
-            {/*  <div className='my-6 sm:my-10'>*/}
-            {/*    <MainTitle title="产品中心" titleLetter="PRODUCT_CENTER"/>*/}
-            {/*  </div>*/}
-            {/*</AnimatedSection>*/}
-
-            <AnimatedSection effect="fade">
-                <div className='my-6 sm:my-10'>
-                    <MainTitle title="核心产品" titleLetter="CORE_PRODUCTS"/>
-                </div>
-            </AnimatedSection>
-
-            {/*核心产品的产品介绍图*/}
-            <AnimatedSection effect="slide" direction="right">
-                <CoreProducts products={coreHardwareProducts}/>
-            </AnimatedSection>
-
-
-            {/*软件产品模块*/}
-            <AnimatedSection effect="slide" direction="left">
-                <ProductionSoft softIntroduction={basicInfo.softwareIntroduction || ""}/>
-            </AnimatedSection>
-            {/* 知识产权统计模块 */}
-            <AnimatedSection effect="scale">
-                <IntellectualPropertyStats/>
-            </AnimatedSection>
-            {/* 服务器客户端模块 */}
-            <AnimatedSection effect="scale">
-                <ServerClient/>
-            </AnimatedSection>
-        </>
-    )
+  const basicInfoRes = await serverGet<BasicInfo[]>("/webSite/getBasicInfo", {
+    next: {
+      revalidate: 1800
+    },
+    cache: "force-cache"
+  })
+
+  const res = await serverGet<ProductCategory[]>("/webSite/getProductCategoryAndType", null, {
+    next: {
+      revalidate: 180
+    },
+    cache: "force-cache"
+  })
+  if (res.code !== 200) {
+    return <div>服务器错误</div>
+  }
+
+  const honorRes = await serverGet<HonorInfo[]>("/webSite/getHonor", {
+    next: {
+      revalidate: 1800
+    },
+    cache: "force-cache"
+  })
+
+  const productList = res.data
+
+  const basicInfo = basicInfoRes.data?.[0] || {
+    address: "",
+    companyProfile: "",
+    companyProfileUrl: "",
+    consultationHotline: "",
+    email: "",
+    hardwareIntroduction: "",
+    id: "",
+    qrCodeUrl: "",
+    serviceHotline: "",
+    softwareIntroduction: "",
+    telephone: ""
+  };
+  return (
+    <>
+      <AnimatedSection effect="fade">
+        <BannerCarousel/>
+      </AnimatedSection>
+
+
+      <AnimatedSection effect="slide" direction="left">
+        <div className="bg-[url('/assets/home/4.png')] w-full">
+          <div className="custom-scrollbar overflow-x-auto">
+            <div
+              className="flex space-x-5 sm:space-x-6 md:space-x-8 min-w-max px-5 sm:px-6 md:px-10 mx-auto w-max">
+              {features.map((f, idx) => (
+                <FeatureCard key={idx} {...f} />
+              ))}
+            </div>
+          </div>
+        </div>
+      </AnimatedSection>
+
+      {/*<AnimatedSection effect="fade">*/}
+      {/*  <div className='my-6 sm:my-10'>*/}
+      {/*    <MainTitle title="产品中心" titleLetter="PRODUCT_CENTER"/>*/}
+      {/*  </div>*/}
+      {/*</AnimatedSection>*/}
+
+      <AnimatedSection effect="fade">
+        <div className='my-6 sm:my-10'>
+          <MainTitle title="核心产品" titleLetter="CORE_PRODUCTS"/>
+        </div>
+      </AnimatedSection>
+
+      {/*核心产品的产品介绍图*/}
+      <AnimatedSection effect="slide" direction="right">
+        <CoreProducts products={productList}/>
+      </AnimatedSection>
+
+
+      {/*软件产品模块*/}
+      <AnimatedSection effect="slide" direction="left">
+        <ProductionSoft softIntroduction={basicInfo.softwareIntroduction || ""}/>
+      </AnimatedSection>
+      {/* 知识产权统计模块 */}
+      <AnimatedSection effect="scale">
+        <IntellectualPropertyStats/>
+      </AnimatedSection>
+      {/* 荣誉资质模块 */}
+      <AnimatedSection effect="scale">
+        <div className='my-6 sm:mt-10'>
+          <MainTitle title="荣誉资质" titleLetter="HONOR"/>
+        </div>
+        <div className={"scroll-mt-10 max-w-4/5 mx-auto py-12 px-4"}>
+          <Honor honorList={honorRes.data}/>
+        </div>
+      </AnimatedSection>
+      {/* 服务器客户端模块 */}
+      <AnimatedSection effect="scale">
+        <ServerClient/>
+      </AnimatedSection>
+    </>
+  )
 }

+ 69 - 8
src/components/CoreProducts.tsx

@@ -1,14 +1,57 @@
 'use client'
-import React from 'react';
+import React, {useEffect, useMemo, useState} from 'react';
 import Link from 'next/link';
 import Image from 'next/image';
 
 interface ProductMenuProps {
-  products: ProductItem[]
+  products: ProductCategory[]
 }
 
 export default function CoreProducts({products}: ProductMenuProps) {
+
   const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL as string
+
+  products = products.reverse()
+
+  const tabs = useMemo(() => {
+    const allTypes = (products ?? []).flatMap((category) => category.productTypes ?? [])
+    return Array.from(new Set(allTypes.map((type) => type.productTypeName)))
+  }, [products])
+
+  const [activeTab, setActiveTab] = useState(() => tabs[0] ?? "")
+
+  useEffect(() => {
+    if (tabs.length === 0) return
+    if (!activeTab || !tabs.includes(activeTab)) {
+      setActiveTab(tabs[0])
+    }
+  }, [tabs, activeTab])
+
+  useEffect(() => {
+    if (tabs.length === 0) return
+
+    const intervalId = setInterval(() => {
+      setActiveTab((prev) => {
+        const currentIndex = tabs.indexOf(prev)
+        const safeIndex = currentIndex === -1 ? 0 : currentIndex
+        const nextIndex = (safeIndex + 1) % tabs.length
+        return tabs[nextIndex]
+      })
+    }, 5000)
+
+    return () => clearInterval(intervalId)
+  }, [tabs])
+
+  const showProducts = useMemo(() => {
+    if (!activeTab) return []
+    const allTypes = (products ?? []).flatMap((category) => category.productTypes ?? [])
+    const matched = allTypes
+      .filter((type) => type.productTypeName === activeTab)
+      .flatMap((type) => type.productCenters ?? [])
+
+    return matched.slice(0, 4)
+  }, [products, activeTab])
+
   return (
     <section className="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
       <div className="flex justify-end mb-5 sm:mb-8">
@@ -21,12 +64,30 @@ export default function CoreProducts({products}: ProductMenuProps) {
         </Link>
       </div>
 
-      {/* 产品列表 */}
-      <div className="grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-6">
-        {products.map((product) => (
+      <div className="w-full border border-gray-300 rounded-md overflow-hidden">
+        <div className="flex overflow-x-auto">
+          {tabs.map((tab) => (
+            <button
+              key={tab}
+              type="button"
+              onClick={() => setActiveTab(tab)}
+              className={`flex-1 min-w-36 sm:min-w-44 py-3 sm:py-4 text-center shuheiti transition-colors cursor-pointer ${
+                activeTab === tab
+                  ? "bg-blue-600 text-white font-semibold"
+                  : "bg-white text-gray-700 hover:bg-gray-100"
+              }`}
+            >
+              {tab}
+            </button>
+          ))}
+        </div>
+      </div>
+
+      <div className="mt-4 sm:mt-6 grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-6">
+        {showProducts.map((product) => (
           <div
             key={product.productId}
-            className="group rounded-xl border border-gray-200 bg-white p-4 sm:p-6 flex flex-col items-center text-center shadow-sm hover:shadow-md transition-shadow duration-300"
+            className="group rounded-xl border border-gray-200 bg-white p-4 sm:p-6 h-80 sm:h-96 flex flex-col items-center text-center justify-between shadow-sm hover:shadow-md transition-shadow duration-300"
           >
             <h3 className="shuheiti text-sm sm:text-xl text-gray-900 line-clamp-1">
               {product.productName}
@@ -41,14 +102,14 @@ export default function CoreProducts({products}: ProductMenuProps) {
                 alt={product.productType}
                 width={160}
                 height={160}
-                className="object-contain transition-transform duration-300 group-hover:scale-[1.03]"
+                className="object-contain scale-105 transition-transform duration-300"
               />
             </div>
             <div className="mt-3 sm:mt-5 w-full flex flex-col items-center gap-1">
                             <span className="text-xs sm:text-sm text-gray-700 line-clamp-1">
                                 {product.productType}
                             </span>
-              <span className="text-[11px] sm:text-xs text-gray-400 line-clamp-1">
+              <span className="text-xs text-gray-400 line-clamp-1">
                                 {product.productModel}
                             </span>
             </div>

+ 1 - 3
src/components/IntellectualPropertyStats.tsx

@@ -1,5 +1,3 @@
-import SubTitle from "@/components/subTitle"
-
 export function IntellectualPropertyStats() {
   const stats = [
     {
@@ -29,7 +27,7 @@ export function IntellectualPropertyStats() {
       {/*<div className={"hidden sm:block w-4/5 mx-auto mt-10"}>*/}
       {/*  <SubTitle title="知识产权" />*/}
       {/*</div>*/}
-      <div className="sm:mt-8 py-8 sm:py-16 px-4 bg-[url('/assets/home/23.png')] bg-cover flex items-center justify-center">
+      <div className="sm:mt-12 py-8 sm:py-20 px-4 bg-[url('/assets/home/23.png')] bg-cover flex items-center justify-center">
         <div className="w-full sm:w-7/12 mx-auto text-center">
           <div className="mb-8 sm:mb-16">
             <h2 className="text-xl sm:text-2xl font-bold text-[#0f397b] mb-2 text-balance shuheiti">知识产权</h2>

+ 0 - 1
src/types/index.ts

@@ -197,7 +197,6 @@ interface Solution {
 // 产品详细信息接口
 interface ProductItem {
     // 产品唯一标识符
-    isCore: string;
     productId: string;
     // 产品名称
     productName: string;

+ 1 - 1
tsconfig.json

@@ -1,6 +1,6 @@
 {
   "compilerOptions": {
-    "target": "ES5",
+    "target": "ES2017",
     "lib": [
       "dom",
       "dom.iterable",