CoreProducts.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. 'use client'
  2. import React, {useEffect, useMemo, useState} from 'react';
  3. import Link from 'next/link';
  4. import Image from 'next/image';
  5. interface ProductMenuProps {
  6. products: ProductCategory[]
  7. }
  8. export default function CoreProducts({products}: ProductMenuProps) {
  9. const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL as string
  10. const filteredProducts = useMemo(() => {
  11. return (products ?? []).filter((item) => item.productCategoryName !== "软件产品")
  12. }, [products])
  13. const orderedProducts = useMemo(() => {
  14. return [...filteredProducts].reverse()
  15. }, [filteredProducts])
  16. const tabs = useMemo(() => {
  17. const allTypes = (orderedProducts ?? []).flatMap((category) => category.productTypes ?? [])
  18. return Array.from(new Set(allTypes.map((type) => type.productTypeName)))
  19. }, [orderedProducts])
  20. const [activeTab, setActiveTab] = useState(() => tabs[0] ?? "")
  21. const [isPaused, setIsPaused] = useState(false)
  22. useEffect(() => {
  23. if (tabs.length === 0) return
  24. if (!activeTab || !tabs.includes(activeTab)) {
  25. setActiveTab(tabs[0])
  26. }
  27. }, [tabs, activeTab])
  28. useEffect(() => {
  29. if (tabs.length === 0 || isPaused) return
  30. const intervalId = setInterval(() => {
  31. setActiveTab((prev) => {
  32. const currentIndex = tabs.indexOf(prev)
  33. const safeIndex = currentIndex === -1 ? 0 : currentIndex
  34. const nextIndex = (safeIndex + 1) % tabs.length
  35. return tabs[nextIndex]
  36. })
  37. }, 5000)
  38. return () => clearInterval(intervalId)
  39. }, [tabs, isPaused])
  40. const showProducts = useMemo(() => {
  41. if (!activeTab) return []
  42. const allTypes = (orderedProducts ?? []).flatMap((category) => category.productTypes ?? [])
  43. const matched = allTypes
  44. .filter((type) => type.productTypeName === activeTab)
  45. .flatMap((type) => type.productCenters ?? [])
  46. return matched.slice(0, 4)
  47. }, [orderedProducts, activeTab])
  48. return (
  49. <section className="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
  50. <div className="w-full border border-gray-300 rounded-md overflow-hidden">
  51. <div className="flex overflow-x-auto">
  52. {tabs.map((tab) => (
  53. <div
  54. key={tab}
  55. onClick={() => setActiveTab(tab)}
  56. className={`flex-1 min-w-36 sm:min-w-44 py-3 sm:py-4 text-center shuheiti transition-colors cursor-pointer ${
  57. activeTab === tab
  58. ? "bg-blue-600 text-white font-semibold"
  59. : "bg-white text-gray-700 hover:bg-gray-100"
  60. }`}
  61. >
  62. {tab}
  63. </div>
  64. ))}
  65. </div>
  66. </div>
  67. <div
  68. className="mt-4 sm:mt-6 grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-6"
  69. onMouseEnter={() => setIsPaused(true)}
  70. onMouseLeave={() => setIsPaused(false)}
  71. >
  72. {showProducts.map((product) => (
  73. <Link
  74. key={product.productId}
  75. href={`/products/${product.productId}`}
  76. className="group rounded-xl border border-gray-200 bg-white p-4 sm:p-6 flex flex-col items-center text-center justify-between shadow-sm hover:shadow-md transition-shadow duration-300 cursor-pointer"
  77. >
  78. <h3 className="shuheiti text-sm sm:text-xl text-gray-900 line-clamp-1">
  79. {product.productName}
  80. </h3>
  81. <p className="mt-1 text-xs sm:text-sm text-gray-500 w-full">
  82. <span className="text-blue-800 font-bold line-clamp-2">{product.productIntroduction}</span>
  83. </p>
  84. <div className="mt-3 sm:mt-5 w-full overflow-hidden rounded-lg bg-gray-50">
  85. <Image
  86. src={product.productUrl ? BASE_URL + product.productUrl : "/assets/productions/2.png"}
  87. alt={product.productName}
  88. width={1000}
  89. height={1000}
  90. sizes="(max-width: 640px) 50vw, 25vw"
  91. className="w-full h-auto object-contain p-2 transition-transform duration-300 group-hover:scale-105"
  92. />
  93. </div>
  94. <div className="mt-3 sm:mt-5 w-full flex flex-col items-center gap-1">
  95. <span className="text-xs sm:text-sm text-gray-700 line-clamp-1">
  96. {product.productType}
  97. </span>
  98. <span className="text-xs text-gray-400 line-clamp-1">
  99. {product.productModel}
  100. </span>
  101. </div>
  102. </Link>
  103. ))}
  104. </div>
  105. </section>
  106. );
  107. };