瀏覽代碼

feat(products): 添加产品展示页面及相关组件- 新增 OriginalProductShowcase 组件用于展示软件与硬件产品- 实现桌面端与移动端响应式菜单导航
- 集成 framer-motion 动画库支持过渡效果
-为招聘信息详情页添加数据缓存与重新验证策略
-优化页面布局与间距适配不同屏幕尺寸

nahida 7 月之前
父節點
當前提交
b3269da80b
共有 3 個文件被更改,包括 303 次插入7 次删除
  1. 1 0
      package.json
  2. 16 7
      src/app/about/recruitList/[id]/page.tsx
  3. 286 0
      src/components/products/OriginalProductShowcase.tsx

+ 1 - 0
package.json

@@ -13,6 +13,7 @@
     "@ant-design/v5-patch-for-react-19": "^1.0.3",
     "antd": "^5.27.2",
     "axios": "^1.11.0",
+    "framer-motion": "^12.23.19",
     "lucide-react": "^0.542.0",
     "next": "15.5.2",
     "react": "19.1.0",

+ 16 - 7
src/app/about/recruitList/[id]/page.tsx

@@ -7,8 +7,13 @@ export const dynamicParams = true
 
 export async function generateStaticParams() {
   const res = await serverPost<Page<RecruitmentInfo>, { pageNum: number; pageSize: number }>(
-    "webSite/getRecruitmentInfoByPage",
-    { pageNum: 1, pageSize: 20 }
+    "/webSite/getRecruitmentInfoByPage",
+    { pageNum: 1, pageSize: 20 },{
+      next: {
+        revalidate: 30
+      },
+      cache: "force-cache"
+    }
   )
 
   return res.data.records.map((item) => ({
@@ -23,8 +28,13 @@ export default async function RecruitmentDetailPage({
 }) {
   const { id } = await params
   const detail = await serverGet<RecruitmentInfo, { id: string }>(
-    "webSite/getRecruitmentInfoById",
-    { id }
+    "/webSite/getRecruitmentInfoById",
+    { id },{
+      next: {
+        revalidate: 30
+      },
+      cache: "force-cache"
+    }
   )
 
   if (!detail) {
@@ -36,10 +46,9 @@ export default async function RecruitmentDetailPage({
   }
 
   return (
-
     <>
       <div className="w-4/5 mx-auto">
-        <div className="pt-10 ml-20 flex gap-2">
+        <div className="pt-5 sm:pt-10 sm:ml-20 flex gap-2">
           <span className="text-sm">您当前的所在位置:</span>
           <Breadcrumb
             separator=">"
@@ -51,7 +60,7 @@ export default async function RecruitmentDetailPage({
           />
         </div>
       </div>
-      <div className="w-11/12 lg:w-4/5 mx-auto py-12">
+      <div className="w-11/12 lg:w-4/5 mx-auto sm:py-12 py-6">
         {/* 卡片 */}
         <div className="bg-white shadow-2xl rounded-2xl p-6 sm:p-10 relative overflow-hidden">
           {/* 蓝色渐变背景 */}

+ 286 - 0
src/components/products/OriginalProductShowcase.tsx

@@ -0,0 +1,286 @@
+"use client"
+
+import {useState} from "react"
+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
+  }
+
+  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",
+    },
+  ]
+
+  const menuItems = [
+    {
+      key: "software",
+      label: "软件产品",
+    },
+    {
+      key: "hardware",
+      label: "硬件产品",
+      children: [
+        {
+          key: "sensor",
+          label: "传感设备",
+        },
+        {
+          key: "iot",
+          label: "物联设备",
+        },
+        {
+          key: "terminal",
+          label: "智能交互终端",
+        },
+      ],
+    },
+  ]
+
+  const handleMenuClick = (key: string) => {
+    setSelectedKey(key)
+  }
+
+  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>
+  )
+
+  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 />
+        </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>
+          <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>
+    </>
+  )
+}