ソースを参照

跟新公司官网,修改产品以及新闻样式

丁烨烨 5 ヶ月 前
コミット
a5fb410ce9

+ 1 - 1
next-env.d.ts

@@ -1,6 +1,6 @@
 /// <reference types="next" />
 /// <reference types="next/image-types/global" />
-/// <reference path="./.next/types/routes.d.ts" />
+import "./.next/types/routes.d.ts";
 
 // NOTE: This file should not be edited
 // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

+ 1 - 1
package.json

@@ -15,7 +15,7 @@
     "axios": "^1.11.0",
     "framer-motion": "^12.23.19",
     "lucide-react": "^0.542.0",
-    "next": "15.5.2",
+    "next": "16.1.0",
     "react": "19.1.0",
     "react-dom": "19.1.0",
     "tw-animate-css": "^1.3.8"

BIN
public/assets/about/20.png


+ 1 - 1
src/app/about/page.tsx

@@ -149,8 +149,8 @@ export default async function Home() {
             <Honor honorList={honorRes.data}/>
           </section>
         </AnimatedSection>
-        {/* 公司招聘 */}
 
+        {/* 公司招聘 */}
         <AnimatedSection effect="slide" direction="right">
           <section
             id="section-3"

+ 325 - 0
src/app/news/NewsPage.css

@@ -0,0 +1,325 @@
+
+/* 页面容器样式 */
+.news-page {
+    max-width: 1200px;
+    margin: 0 auto;
+    min-height: 100vh;
+}
+
+/* 页面标题样式 */
+.page-title {
+    text-align: center;
+    color: #333;
+    margin-bottom: 40px;
+    font-size: 2.5rem;
+    font-weight: 600;
+    letter-spacing: 1px;
+}
+
+/* 标签切换容器样式 */
+.news-tabs {
+    display: flex;
+    justify-content: center;
+    margin-bottom: 30px;
+    gap: 10px;
+}
+
+/* 切换按钮基础样式 */
+.tab-btn {
+    padding: 12px 30px;
+    border: none;
+    border-radius: 30px;
+    background-color: #e0e0e0;
+    color: #666;
+    font-size: 1rem;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    outline: none;
+}
+
+/* 激活状态按钮样式 */
+.tab-btn.active {
+    background-color: #2f54eb;
+    color: white;
+    box-shadow: 0 4px 12px rgba(47, 84, 235, 0.3);
+}
+
+/* 按钮hover效果 */
+.tab-btn:hover:not(.active) {
+    background-color: #d0d0d0;
+    color: #333;
+}
+
+/* 加载状态样式 */
+.loading {
+    text-align: center;
+    font-size: 1.2rem;
+    color: #666;
+    padding: 40px 0;
+}
+
+/* 无新闻提示样式 */
+.no-news {
+    text-align: center;
+    font-size: 1.2rem;
+    color: #999;
+    padding: 40px 0;
+}
+
+/* 新闻列表容器样式(列表形式) */
+.news-list {
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
+    margin-top: 20px;
+    margin-bottom: 40px;
+}
+
+/* 新闻列表项样式(横向布局:图片+文字) */
+.news-item {
+    display: flex;
+    align-items: flex-start;
+    background-color: white;
+    border-radius: 12px;
+    padding: 20px;
+    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
+    transition: transform 0.3s ease, box-shadow 0.3s ease;
+    gap: 20px;
+}
+
+/* 列表项hover效果 */
+.news-item:hover {
+    transform: translateY(-3px);
+    box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
+}
+
+/* 新闻封面图容器 */
+.news-cover {
+    flex-shrink: 0; /* 防止图片被压缩 */
+    width: 150px;
+    height: 100px;
+    border-radius: 8px;
+    overflow: hidden;
+}
+
+/* 封面图片样式 */
+.news-cover img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover; /* 保持图片比例,填充容器 */
+    transition: transform 0.3s ease;
+}
+
+/* 图片hover放大效果 */
+.news-item:hover .news-cover img {
+    transform: scale(1.05);
+}
+
+/* 新闻内容容器 */
+.news-content-wrapper {
+    flex: 1; /* 占满剩余空间 */
+    overflow: hidden;
+}
+
+/* 新闻标题样式 */
+.news-title {
+    color: #333;
+    font-size: 1.2rem;
+    margin-bottom: 10px;
+    line-height: 1.4;
+    font-weight: 600;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis; /* 标题过长显示省略号 */
+}
+
+/* 新闻元信息(日期、作者)样式 */
+.news-meta {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 8px;
+    font-size: 0.85rem;
+    color: #999;
+}
+
+.news-date{
+    margin: 0 20px;
+}
+
+.news-author{
+    white-space: nowrap;
+}
+
+/* 新闻内容样式 */
+.news-content {
+    color: #666;
+    line-height: 1.6;
+    font-size: 0.95rem;
+    display: -webkit-box;
+    -webkit-line-clamp: 2; /* 内容最多显示2行 */
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+/* 分页容器样式 */
+.pagination-container {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 20px 0;
+    flex-wrap: wrap;
+    gap: 20px;
+}
+
+/* 每页条数选择器 */
+.page-size-selector {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    color: #666;
+    font-size: 0.95rem;
+}
+
+.page-size-select {
+    padding: 6px 10px;
+    border: 1px solid #ddd;
+    border-radius: 6px;
+    background-color: white;
+    color: #666;
+    cursor: pointer;
+    font-size: 0.95rem;
+}
+
+/* 分页控件 */
+.pagination-controls {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+}
+
+.page-btn {
+    padding: 8px 16px;
+    border: 1px solid #ddd;
+    border-radius: 6px;
+    background-color: white;
+    color: #666;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    font-size: 0.95rem;
+}
+
+.page-btn:hover:not(:disabled) {
+    background-color: #f0f0f0;
+    color: #333;
+}
+
+.page-btn:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+}
+
+/* 页码容器 */
+.page-numbers {
+    display: flex;
+    gap: 5px;
+}
+
+.page-number-btn {
+    width: 36px;
+    height: 36px;
+    border: 1px solid #ddd;
+    border-radius: 6px;
+    background-color: white;
+    color: #666;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 0.95rem;
+}
+
+.page-number-btn.active {
+    background-color: #2f54eb;
+    color: white;
+    border-color: #2f54eb;
+}
+
+.page-number-btn:hover:not(.active):not(.ellipsis) {
+    background-color: #f0f0f0;
+    color: #333;
+}
+
+.page-number-btn.ellipsis {
+    border: none;
+    cursor: default;
+    background: transparent;
+}
+
+.page-number-btn:disabled {
+    opacity: 1;
+    cursor: default;
+}
+
+/* 页码信息 */
+.page-info {
+    color: #666;
+    font-size: 0.95rem;
+}
+
+/* 响应式适配 */
+@media (max-width: 768px) {
+    .page-title {
+        font-size: 2rem;
+    }
+
+    .tab-btn {
+        padding: 10px 20px;
+        font-size: 0.9rem;
+    }
+
+    /* 移动端改为纵向布局:图片在上,文字在下 */
+    .news-item {
+        flex-direction: column;
+        padding: 15px;
+        gap: 15px;
+    }
+
+    .news-cover {
+        width: 100%;
+        height: 180px;
+    }
+
+    .news-title {
+        font-size: 1.1rem;
+        white-space: normal; /* 移动端标题允许换行 */
+        -webkit-line-clamp: 2;
+        display: -webkit-box;
+        white-space: normal;
+    }
+
+    .news-content {
+        font-size: 0.9rem;
+        -webkit-line-clamp: 3; /* 移动端内容显示3行 */
+    }
+
+    /* 移动端分页布局调整 */
+    .pagination-container {
+        flex-direction: column;
+        align-items: center;
+        gap: 15px;
+    }
+
+    .page-size-selector {
+        order: 1;
+    }
+
+    .pagination-controls {
+        order: 2;
+    }
+
+    .page-info {
+        order: 3;
+    }
+}

+ 68 - 59
src/app/news/[id]/page.tsx

@@ -1,77 +1,86 @@
 import React from 'react';
 import {Breadcrumb} from "antd";
 import {serverGet} from "@/utils/request";
-import './rich.css'
+// import './rich.module.scss'
 import MainTitle from "@/components/MainTitle";
 import ContentNotFound from "@/components/ContentNotFound";
 
 export const dynamicParams = true
 
 export const generateStaticParams = async () => {
-  const res = await serverGet<Page<NewsUpdates>, { pageNum: number; pageSize: number }>(
-    "/webSite/getNewsUpdatesListWithoutSpecialNews",
-    {pageNum: 1, pageSize: 20}, {
-      next: {
-        revalidate: 1800
-      },
-      cache: "force-cache"
-    }
-  )
-  const res2 = await serverGet<Page<NewsUpdates>, { pageNum: number; pageSize: number }>(
-    "/webSite/getSpecialNewsUpdatesList",
-    {pageNum: 1, pageSize: 20}, {
-      next: {
-        revalidate: 1800
-      },
-      cache: "force-cache"
-    }
-  )
-  return [...res.data.records, ...res2.data.records].map((item) => ({
-    id: item.id,
-  }))
+    const res = await serverGet<Page<NewsUpdates>, { pageNum: number; pageSize: number }>(
+        "/webSite/getNewsUpdatesListWithoutSpecialNews",
+        {pageNum: 1, pageSize: 20}, {
+            next: {
+                revalidate: 1800
+            },
+            cache: "force-cache"
+        }
+    )
+    const res2 = await serverGet<Page<NewsUpdates>, { pageNum: number; pageSize: number }>(
+        "/webSite/getSpecialNewsUpdatesList",
+        {pageNum: 1, pageSize: 20}, {
+            next: {
+                revalidate: 1800
+            },
+            cache: "force-cache"
+        }
+    )
+    return [...res.data.records, ...res2.data.records].map((item) => ({
+        id: item.id,
+    }))
 }
 
+
 async function Page({
-                      params,
+                        params,
                     }: {
-  params: Promise<{ id: string }>
+    params: Promise<{ id: string }>
 }) {
-  const {id} = await params;
-  const res = await serverGet<NewsUpdates, { id: string }>(
-    "/webSite/getNewsUpdatesById",
-    {id}, {
-      next: {
-        revalidate: 30
-      },
-      cache: "force-cache"
+    const {id} = await params;
+    const res = await serverGet<NewsUpdates, { id: string }>(
+        "/webSite/getNewsUpdatesById",
+        {id}, {
+            next: {
+                revalidate: 30
+            },
+            cache: "force-cache"
+        }
+    )
+    if (!res.data) {
+        return <ContentNotFound/>
     }
-  )
-  if (!res.data) {
-    return <ContentNotFound />
-  }
-  return (
-    <>
-      <div className="w-4/5 mx-auto">
-        <div className="pt-5 sm:pt-10 sm:ml-20 flex gap-2">
-          <span className="text-sm">您当前的所在位置:</span>
-          <Breadcrumb
-            separator=">"
-            items={[
-              {title: "新闻列表", href: "/news"},
-              {title: res.data.newsName || "新闻详情"},
-            ]}
-          />
-        </div>
-      </div>
-      <div className="py-6 sm:py-10">
-        <MainTitle title={"新闻动态"} titleLetter={"NEWS_DYNAMIC"}/>
-      </div>
-      <div
-        dangerouslySetInnerHTML={{__html: res.data.newsDetails as string}}
-        className="ql-editor w-9/10 sm:w-7/10 mx-auto sm:px-20 sm:py-10"
-      />
-    </>
-  );
+
+
+    console.log(111,res)
+    return (
+        <>
+            <div className="w-4/5 mx-auto">
+                <div className="pt-5 sm:pt-10 sm:ml-20 flex gap-2">
+                    <span className="text-sm">您当前的所在位置:</span>
+                    <Breadcrumb
+                        separator=">"
+                        items={[
+                            {title: "新闻列表", href: "/news"},
+                            {title: res.data.newsName || "新闻详情"},
+                        ]}
+                    />
+                </div>
+            </div>
+            <div className="py-6 sm:py-10">
+                <MainTitle title={"新闻动态"} titleLetter={"NEWS_DYNAMIC"}/>
+            </div>
+            <div className="w-4/5 mx-auto flex justify-center items-center">
+                {/*<div className="">*/}
+                    <span className="text-3xl font-bold ">{res.data.newsName}</span>
+                {/*</div>*/}
+            </div>
+            <div
+                dangerouslySetInnerHTML={{__html: res.data.newsDetails as string}}
+                className="ql-editor w-9/10 sm:w-7/10 mx-auto sm:px-20 sm:py-10"
+            />
+        </>
+    );
 }
 
 

ファイルの差分が大きいため隠しています
+ 0 - 0
src/app/news/[id]/rich.css


+ 485 - 0
src/app/news/[id]/rich.module.scss

@@ -0,0 +1,485 @@
+.ql-container {
+    box-sizing: border-box;
+    font-family: Helvetica, Arial, sans-serif;
+    font-size: 13px;
+    height: 100%;
+    margin: 0;
+    position: relative
+}
+
+.ql-container.ql-disabled .ql-tooltip {
+    visibility: hidden
+}
+
+.ql-container.ql-disabled .ql-editor ul[data-checked] > li::before {
+    pointer-events: none
+}
+
+.ql-clipboard {
+    left: -100000px;
+    height: 1px;
+    overflow-y: hidden;
+    position: absolute;
+    top: 50%
+}
+
+.ql-clipboard p {
+    margin: 0;
+    padding: 0
+}
+
+.ql-editor {
+    box-sizing: border-box;
+    line-height: 1.42;
+    height: 100%;
+    outline: none;
+    overflow-y: auto;
+    padding: 12px 15px;
+    tab-size: 4;
+    -moz-tab-size: 4;
+    text-align: left;
+    white-space: pre-wrap;
+    word-wrap: break-word
+}
+
+.ql-editor > * {
+    cursor: text
+}
+
+.ql-editor p, .ql-editor ol, .ql-editor ul, .ql-editor pre, .ql-editor blockquote, .ql-editor h1, .ql-editor h2, .ql-editor h3, .ql-editor h4, .ql-editor h5, .ql-editor h6 {
+    margin: 0;
+    padding: 0;
+    counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9
+}
+
+.ql-editor ol, .ql-editor ul {
+    padding-left: 1.5em
+}
+
+.ql-editor ol > li, .ql-editor ul > li {
+    list-style-type: none
+}
+
+.ql-editor ul > li::before {
+    content: '\2022'
+}
+
+.ql-editor ul[data-checked=true], .ql-editor ul[data-checked=false] {
+    pointer-events: none
+}
+
+.ql-editor ul[data-checked=true] > li *, .ql-editor ul[data-checked=false] > li * {
+    pointer-events: all
+}
+
+.ql-editor ul[data-checked=true] > li::before, .ql-editor ul[data-checked=false] > li::before {
+    color: #777;
+    cursor: pointer;
+    pointer-events: all
+}
+
+.ql-editor ul[data-checked=true] > li::before {
+    content: '\2611'
+}
+
+.ql-editor ul[data-checked=false] > li::before {
+    content: '\2610'
+}
+
+.ql-editor li::before {
+    display: inline-block;
+    white-space: nowrap;
+    width: 1.2em
+}
+
+.ql-editor li:not(.ql-direction-rtl)::before {
+    margin-left: -1.5em;
+    margin-right: .3em;
+    text-align: right
+}
+
+.ql-editor li.ql-direction-rtl::before {
+    margin-left: .3em;
+    margin-right: -1.5em
+}
+
+.ql-editor ol li:not(.ql-direction-rtl), .ql-editor ul li:not(.ql-direction-rtl) {
+    padding-left: 1.5em
+}
+
+.ql-editor ol li.ql-direction-rtl, .ql-editor ul li.ql-direction-rtl {
+    padding-right: 1.5em
+}
+
+.ql-editor ol li {
+    counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
+    counter-increment: list-0
+}
+
+.ql-editor ol li:before {
+    content: counter(list-0, decimal) '. '
+}
+
+.ql-editor ol li.ql-indent-1 {
+    counter-increment: list-1
+}
+
+.ql-editor ol li.ql-indent-1:before {
+    content: counter(list-1, lower-alpha) '. '
+}
+
+.ql-editor ol li.ql-indent-1 {
+    counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9
+}
+
+.ql-editor ol li.ql-indent-2 {
+    counter-increment: list-2
+}
+
+.ql-editor ol li.ql-indent-2:before {
+    content: counter(list-2, lower-roman) '. '
+}
+
+.ql-editor ol li.ql-indent-2 {
+    counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9
+}
+
+.ql-editor ol li.ql-indent-3 {
+    counter-increment: list-3
+}
+
+.ql-editor ol li.ql-indent-3:before {
+    content: counter(list-3, decimal) '. '
+}
+
+.ql-editor ol li.ql-indent-3 {
+    counter-reset: list-4 list-5 list-6 list-7 list-8 list-9
+}
+
+.ql-editor ol li.ql-indent-4 {
+    counter-increment: list-4
+}
+
+.ql-editor ol li.ql-indent-4:before {
+    content: counter(list-4, lower-alpha) '. '
+}
+
+.ql-editor ol li.ql-indent-4 {
+    counter-reset: list-5 list-6 list-7 list-8 list-9
+}
+
+.ql-editor ol li.ql-indent-5 {
+    counter-increment: list-5
+}
+
+.ql-editor ol li.ql-indent-5:before {
+    content: counter(list-5, lower-roman) '. '
+}
+
+.ql-editor ol li.ql-indent-5 {
+    counter-reset: list-6 list-7 list-8 list-9
+}
+
+.ql-editor ol li.ql-indent-6 {
+    counter-increment: list-6
+}
+
+.ql-editor ol li.ql-indent-6:before {
+    content: counter(list-6, decimal) '. '
+}
+
+.ql-editor ol li.ql-indent-6 {
+    counter-reset: list-7 list-8 list-9
+}
+
+.ql-editor ol li.ql-indent-7 {
+    counter-increment: list-7
+}
+
+.ql-editor ol li.ql-indent-7:before {
+    content: counter(list-7, lower-alpha) '. '
+}
+
+.ql-editor ol li.ql-indent-7 {
+    counter-reset: list-8 list-9
+}
+
+.ql-editor ol li.ql-indent-8 {
+    counter-increment: list-8
+}
+
+.ql-editor ol li.ql-indent-8:before {
+    content: counter(list-8, lower-roman) '. '
+}
+
+.ql-editor ol li.ql-indent-8 {
+    counter-reset: list-9
+}
+
+.ql-editor ol li.ql-indent-9 {
+    counter-increment: list-9
+}
+
+.ql-editor ol li.ql-indent-9:before {
+    content: counter(list-9, decimal) '. '
+}
+
+.ql-editor .ql-indent-1:not(.ql-direction-rtl) {
+    padding-left: 3em
+}
+
+.ql-editor li.ql-indent-1:not(.ql-direction-rtl) {
+    padding-left: 4.5em
+}
+
+.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right {
+    padding-right: 3em
+}
+
+.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right {
+    padding-right: 4.5em
+}
+
+.ql-editor .ql-indent-2:not(.ql-direction-rtl) {
+    padding-left: 6em
+}
+
+.ql-editor li.ql-indent-2:not(.ql-direction-rtl) {
+    padding-left: 7.5em
+}
+
+.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right {
+    padding-right: 6em
+}
+
+.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right {
+    padding-right: 7.5em
+}
+
+.ql-editor .ql-indent-3:not(.ql-direction-rtl) {
+    padding-left: 9em
+}
+
+.ql-editor li.ql-indent-3:not(.ql-direction-rtl) {
+    padding-left: 10.5em
+}
+
+.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right {
+    padding-right: 9em
+}
+
+.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right {
+    padding-right: 10.5em
+}
+
+.ql-editor .ql-indent-4:not(.ql-direction-rtl) {
+    padding-left: 12em
+}
+
+.ql-editor li.ql-indent-4:not(.ql-direction-rtl) {
+    padding-left: 13.5em
+}
+
+.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right {
+    padding-right: 12em
+}
+
+.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right {
+    padding-right: 13.5em
+}
+
+.ql-editor .ql-indent-5:not(.ql-direction-rtl) {
+    padding-left: 15em
+}
+
+.ql-editor li.ql-indent-5:not(.ql-direction-rtl) {
+    padding-left: 16.5em
+}
+
+.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right {
+    padding-right: 15em
+}
+
+.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right {
+    padding-right: 16.5em
+}
+
+.ql-editor .ql-indent-6:not(.ql-direction-rtl) {
+    padding-left: 18em
+}
+
+.ql-editor li.ql-indent-6:not(.ql-direction-rtl) {
+    padding-left: 19.5em
+}
+
+.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right {
+    padding-right: 18em
+}
+
+.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right {
+    padding-right: 19.5em
+}
+
+.ql-editor .ql-indent-7:not(.ql-direction-rtl) {
+    padding-left: 21em
+}
+
+.ql-editor li.ql-indent-7:not(.ql-direction-rtl) {
+    padding-left: 22.5em
+}
+
+.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right {
+    padding-right: 21em
+}
+
+.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right {
+    padding-right: 22.5em
+}
+
+.ql-editor .ql-indent-8:not(.ql-direction-rtl) {
+    padding-left: 24em
+}
+
+.ql-editor li.ql-indent-8:not(.ql-direction-rtl) {
+    padding-left: 25.5em
+}
+
+.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right {
+    padding-right: 24em
+}
+
+.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right {
+    padding-right: 25.5em
+}
+
+.ql-editor .ql-indent-9:not(.ql-direction-rtl) {
+    padding-left: 27em
+}
+
+.ql-editor li.ql-indent-9:not(.ql-direction-rtl) {
+    padding-left: 28.5em
+}
+
+.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right {
+    padding-right: 27em
+}
+
+.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right {
+    padding-right: 28.5em
+}
+
+.ql-editor .ql-video {
+    display: block;
+    max-width: 100%
+}
+
+.ql-editor .ql-video.ql-align-center {
+    margin: 0 auto
+}
+
+.ql-editor .ql-video.ql-align-right {
+    margin: 0 0 0 auto
+}
+
+.ql-editor .ql-bg-black {
+    background-color: #000
+}
+
+.ql-editor .ql-bg-red {
+    background-color: #e60000
+}
+
+.ql-editor .ql-bg-orange {
+    background-color: #f90
+}
+
+.ql-editor .ql-bg-yellow {
+    background-color: #ff0
+}
+
+.ql-editor .ql-bg-green {
+    background-color: #008a00
+}
+
+.ql-editor .ql-bg-blue {
+    background-color: #06c
+}
+
+.ql-editor .ql-bg-purple {
+    background-color: #93f
+}
+
+.ql-editor .ql-color-white {
+    color: #fff
+}
+
+.ql-editor .ql-color-red {
+    color: #e60000
+}
+
+.ql-editor .ql-color-orange {
+    color: #f90
+}
+
+.ql-editor .ql-color-yellow {
+    color: #ff0
+}
+
+.ql-editor .ql-color-green {
+    color: #008a00
+}
+
+.ql-editor .ql-color-blue {
+    color: #06c
+}
+
+.ql-editor .ql-color-purple {
+    color: #93f
+}
+
+.ql-editor .ql-font-serif {
+    font-family: Georgia, Times New Roman, serif
+}
+
+.ql-editor .ql-font-monospace {
+    font-family: Monaco, Courier New, monospace
+}
+
+.ql-editor .ql-size-small {
+    font-size: .75em
+}
+
+.ql-editor .ql-size-large {
+    font-size: 1.5em
+}
+
+.ql-editor .ql-size-huge {
+    font-size: 2.5em
+}
+
+.ql-editor .ql-direction-rtl {
+    direction: rtl;
+    text-align: inherit
+}
+
+.ql-editor .ql-align-center {
+    text-align: center
+}
+
+.ql-editor .ql-align-justify {
+    text-align: justify
+}
+
+.ql-editor .ql-align-right {
+    text-align: right
+}
+
+.ql-editor.ql-blank::before {
+    color: rgba(0, 0, 0, 0.6);
+    content: attr(data-placeholder);
+    font-style: italic;
+    left: 15px;
+    pointer-events: none;
+    position: absolute;
+    right: 15px
+}

+ 156 - 0
src/app/news/news.tsx

@@ -0,0 +1,156 @@
+"use client";
+import React, {useState, useEffect, useMemo} from 'react';
+import './NewsPage.css';
+import Link from "next/link";
+
+// 1. 抽离类型定义,提升可维护性
+interface Article {
+    id: string;
+    newsUrl: string;
+    newsName: string;
+    newsDesc: string;
+    newsAuthor?: string; // 可选字段
+    content: string;
+    newsCategory: string; // 补充分类字段(原逻辑依赖)
+}
+
+// 2. 定义Props类型,避免隐式any
+interface NewsItemProps {
+    news: Article,
+    key?: string
+}
+
+interface NewsListProps {
+    newsList: Article[];
+}
+
+interface NewsPageProps {
+    newsList: Article[];
+}
+
+// 3. 抽离常量,便于统一维护
+const CATEGORY_MAP = Object.freeze({
+    company: '公司新闻',
+    industry: '行内新闻'
+} as const); // 常量断言,锁定类型
+
+// 4. 新闻列表项组件(纯组件,优化性能)
+const NewsItem: React.FC<NewsItemProps> = ({news: article, key}) => {
+    // 环境变量默认值兜底,避免undefined
+    const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || '';
+
+    // 图片地址处理抽离,提升可读性
+    const coverImageUrl = article.newsUrl ? `${BASE_URL}${article.newsUrl}` : '';
+
+    return (
+        <div className="news-item">
+            <div className="news-cover">
+                <img
+                    src={coverImageUrl}
+                    alt={article.newsName} // 补充有意义的alt属性,优化可访问性
+                    loading="lazy"
+                    onError={(e) => {
+                        // 图片加载失败兜底,提升用户体验
+                        (e.target as HTMLImageElement).src = '/default-news-cover.png';
+                    }}
+                />
+            </div>
+            <div className="news-content-wrapper">
+                <h3 className="news-title">{article.newsName}</h3>
+                <div className="news-meta">
+                    <span className="news-date">{article.newsDesc}</span>
+                    {/* 可选字段兜底,避免空值展示 */}
+                    <span className="news-author">作者:{article.newsAuthor || '未知'}</span>
+                </div>
+                <p className="news-content">{article.content}</p>
+            </div>
+            <Link
+                href={`/news/${article.id}`}
+                className="text-blue-500 hover:underline mt-4 self-start text-sm sm:text-base"
+                aria-label={`查看${article.newsName}详情`} // 优化可访问性
+            >
+                了解更多 &gt;
+            </Link>
+        </div>
+    );
+};
+
+// 5. 新闻列表组件(添加空状态优化)
+const NewsList: React.FC<NewsListProps> = ({newsList}) => {
+    // 空数组判断优化,避免length判断出错
+    if (!Array.isArray(newsList) || newsList.length === 0) {
+        return <div className="no-news">暂无相关新闻</div>;
+    }
+
+    return (
+        <div className="news-list">
+            {newsList.map((news) => (
+                <NewsItem
+                    key={news.id}
+                    news={news}
+                />
+            ))}
+        </div>
+    );
+};
+
+// 6. 主新闻页面组件
+const NewsPage: React.FC<NewsPageProps> = ({newsList = []}) => {
+    // 类型守卫,确保newsList是数组类型
+    const safeNewsList = useMemo(() =>
+            Array.isArray(newsList) ? newsList : [],
+        [newsList]);
+
+    const [activeTab, setActiveTab] = useState<keyof typeof CATEGORY_MAP>('company'); // 严格类型约束
+    const [filteredNews, setFilteredNews] = useState<Article[]>([]);
+    const [isLoading, setIsLoading] = useState(true);
+
+    // 7. 优化筛选逻辑:使用useMemo减少重复计算
+    useEffect(() => {
+        setIsLoading(true);
+
+        const timer = setTimeout(() => {
+            const targetCategory = CATEGORY_MAP[activeTab];
+            const filtered = safeNewsList.filter(
+                (news) => news.newsCategory === targetCategory
+            );
+
+            setFilteredNews(filtered);
+            setIsLoading(false);
+        }, 300);
+
+        return () => clearTimeout(timer);
+    }, [safeNewsList, activeTab]);
+
+    // 8. 抽离标签切换逻辑,简化代码
+    const handleTabChange = (tab: keyof typeof CATEGORY_MAP) => {
+        setIsLoading(true);
+        setActiveTab(tab);
+    };
+
+    return (
+        <div className="news-page">
+            <div className="news-tabs">
+                <button
+                    className={`tab-btn ${activeTab === 'company' ? 'active' : ''}`}
+                    onClick={() => handleTabChange('company')}
+                    aria-pressed={activeTab === 'company'} // 优化可访问性
+                >
+                    公司新闻
+                </button>
+                <button
+                    className={`tab-btn ${activeTab === 'industry' ? 'active' : ''}`}
+                    onClick={() => handleTabChange('industry')}
+                    aria-pressed={activeTab === 'industry'}
+                >
+                    行内新闻
+                </button>
+            </div>
+
+            {isLoading && <div className="loading">加载中...</div>}
+            {!isLoading && <NewsList newsList={filteredNews}/>}
+        </div>
+    );
+};
+
+export default NewsPage;

+ 115 - 97
src/app/news/page.tsx

@@ -6,112 +6,130 @@ import PaginationClient from "@/components/PaginationClient";
 import {serverGet} from "@/utils/request";
 import {removeHTMLTags} from "@/utils/removeHTMLTags";
 import AnimatedSection from "@/components/AnimatedSection";
-
-const getNewsList =async (pageNum: number, pageSize: number) =>{
-  return serverGet<Page<NewsUpdates>>("/webSite/getNewsUpdatesListWithoutSpecialNews",{
-    pageNum,
-    pageSize
-  },{
-    next: {
-      revalidate: 180
-    },
-    cache: "force-cache"
-  })
+import News from "./news";
+// 调整NewsUpdates类型,对齐Article的结构
+interface NewsUpdates {
+    id: string;
+    newsUrl: string;
+    newsName: string;
+    newsDesc: string;
+    newsAuthor?: string; // 可选字段
+    content: string;
+    newsCategory: string; // 补充分类字段(原逻辑依赖)
+}
+const getNewsList = async (pageNum: number, pageSize: number) => {
+    return serverGet<Page<NewsUpdates>>("/webSite/getNewsUpdatesListWithoutSpecialNews", {
+        pageNum,
+        pageSize
+    }, {
+        next: {
+            revalidate: 180
+        },
+        cache: "force-cache"
+    })
 }
 
-const getSpecialNews = async () =>{
-  return serverGet<Page<NewsUpdates>>("/webSite/getSpecialNewsUpdatesList",{
-    pageNum: 1,
-    pageSize: 1
-  },{
-    next: {
-      revalidate: 180
-    },
-    cache: "force-cache"
-  })
+const getSpecialNews = async () => {
+    return serverGet<Page<NewsUpdates>>("/webSite/getSpecialNewsUpdatesList", {
+        pageNum: 1,
+        pageSize: 1
+    }, {
+        next: {
+            revalidate: 180
+        },
+        cache: "force-cache"
+    })
 }
 
 export default async function NewsPage({
-                                   searchParams,
-                                 }: {
-  searchParams: Promise<{ page?: string }>
+                                           searchParams,
+                                       }: {
+    searchParams: Promise<{ page?: string }>
 }) {
 
-  const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
-  // 等待 searchParams 解析
-  const params = await searchParams
-  const pageNum = Number.parseInt(params.page || "1", 10)
-  const pageSize = 6
-  const newsListRes = await getNewsList(pageNum, pageSize)
-  const specialNewsRes = await getSpecialNews()
-  const specialNews = specialNewsRes.data.records[0]
-  const newsList = newsListRes.data.records
-  const totalNews = newsListRes.data.total
+    const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
+    // 等待 searchParams 解析
+    const params = await searchParams
+    const pageNum = Number.parseInt(params.page || "1", 10)
+    const pageSize = 6
+    const newsListRes = await getNewsList(pageNum, pageSize)
+    const specialNewsRes = await getSpecialNews()
+    const specialNews = specialNewsRes.data.records[0]
+    const newsList = newsListRes.data.records
+    const totalNews = newsListRes.data.total
 
-  return (
-    <>
-      <AnimatedSection effect="slide" direction="left">
-        <div className="w-full h-full flex items-center justify-center text-white text-4xl font-bold">
-          <Image src={"/assets/news/1.png"} alt={"banner"} width={1920} height={1080}/>
-        </div>
-      </AnimatedSection>
-
-      <div className="py-6 sm:py-10">
-        <MainTitle title={"新闻动态"} titleLetter={"NEWS_DYNAMIC"}/>
-      </div>
-      <AnimatedSection effect="slide" direction="right">
-      <div className="container sm:max-w-7/12 mx-auto p-4 space-y-6">
-        {/* 顶部大新闻 */}
-        <div className="bg-white rounded-2xl p-6">
-          <div className="flex flex-col sm:flex-row gap-6">
-            {/* 模拟图片色块 */}
-            <div className="w-full sm:w-1/3 bg-gray-300 rounded-lg sm:h-auto flex items-center justify-center">
-              <Image src={specialNews?.newsUrl ? BASE_URL + specialNews?.newsUrl : ""} alt={"特别新闻图片"} width={300} height={300} />
-            </div>
-            {/* 右侧文字 */}
-            <div className="flex-1 flex flex-col justify-between">
-              <div>
-                <h2 className="text-lg sm:text-xl font-bold mb-2">{specialNews?.newsName}</h2>
-                <p className="text-gray-500 text-xs sm:text-sm mb-4">{specialNews?.releaseTime}</p>
-                <p className="text-gray-700 text-sm sm:text-base leading-relaxed line-clamp-7">
-                  {removeHTMLTags(specialNews?.newsDetails || "")}
-                </p>
-              </div>
-              <Link href={`/news/${specialNews?.id}`} className="text-blue-500 hover:underline mt-4 self-start text-sm sm:text-base">
-                了解更多 &gt;
-              </Link>
+    return (
+        <>
+            <AnimatedSection effect="slide" direction="left">
+                <div className="w-full h-full flex items-center justify-center text-white text-4xl font-bold">
+                    <Image src={"/assets/news/1.png"} alt={"banner"} width={1920} height={1080}/>
+                </div>
+            </AnimatedSection>
+            <div className="py-6 sm:py-10">
+                <MainTitle title={"新闻动态"} titleLetter={"NEWS_DYNAMIC"}/>
             </div>
-          </div>
-        </div>
+            <News newsList={newsList}></News>
 
-        {/* 下方小新闻卡片 */}
-        <div className="grid sm:grid-cols-2 gap-4">
-          {newsList.map((item) => (
-            <div
-              key={item.id}
-              className="bg-white rounded-2xl shadow p-4 flex flex-col justify-between"
-            >
-              <div>
-                <h3 className="text-sm sm:text-base font-semibold mb-2">{item.newsName}</h3>
-                <p className="text-gray-500 text-xs sm:text-sm">{item.releaseTime}</p>
-              </div>
-              <Link href={`/news/${item.id}`} className="text-blue-500 hover:underline mt-2 self-end text-xs sm:text-sm">
-                了解更多 &gt;
-              </Link>
-            </div>
-          ))}
-        </div>
 
-        {/* 分页器 */}
-        <div className="flex justify-center mt-6">
-          <PaginationClient
-            current={pageNum}
-            total={totalNews}
-            pageSize={pageSize}
-          />
-        </div>
-      </div>
-        </AnimatedSection>
-    </>
-  )
+            {/*<AnimatedSection effect="slide" direction="right">*/}
+            {/*    /!*<div className="container sm:max-w-7/12 mx-auto p-4 space-y-6">*!/*/}
+            {/*    <div className="sm:max-w-7/12 mx-auto p-4 space-y-6">*/}
+            {/*        /!* 顶部大新闻 *!/*/}
+            {/*        <div className="bg-white rounded-2xl p-6">*/}
+            {/*            <div className="flex flex-col sm:flex-row gap-6">*/}
+            {/*                /!* 模拟图片色块 *!/*/}
+            {/*                <div*/}
+            {/*                    className="w-full sm:w-1/3 bg-gray-300 rounded-lg sm:h-auto flex items-center justify-center">*/}
+            {/*                    <Image src={specialNews?.newsUrl ? BASE_URL + specialNews?.newsUrl : ""}*/}
+            {/*                           alt={"特别新闻图片"}*/}
+            {/*                           width={300} height={300}/>*/}
+            {/*                </div>*/}
+            {/*                /!* 右侧文字 *!/*/}
+            {/*                <div className="flex-1 flex flex-col justify-between">*/}
+            {/*                    <div>*/}
+            {/*                        <h2 className="text-lg sm:text-xl font-bold mb-2">{specialNews?.newsName}</h2>*/}
+            {/*                        <p className="text-gray-500 text-xs sm:text-sm mb-4">{specialNews?.releaseTime}</p>*/}
+            {/*                        <p className="text-gray-700 text-sm sm:text-base leading-relaxed line-clamp-7">*/}
+            {/*                            {removeHTMLTags(specialNews?.newsDetails || "")}*/}
+            {/*                        </p>*/}
+            {/*                    </div>*/}
+            {/*                    <Link href={`/news/${specialNews?.id}`}*/}
+            {/*                          className="text-blue-500 hover:underline mt-4 self-start text-sm sm:text-base">*/}
+            {/*                        了解更多 &gt;*/}
+            {/*                    </Link>*/}
+            {/*                </div>*/}
+            {/*            </div>*/}
+            {/*        </div>*/}
+
+            {/*        /!* 下方小新闻卡片 *!/*/}
+            {/*        <div className="grid sm:grid-cols-2 gap-4">*/}
+            {/*            {newsList.map((item) => (*/}
+            {/*                <div*/}
+            {/*                    key={item.id}*/}
+            {/*                    className="bg-white rounded-2xl shadow p-4 flex flex-col justify-between"*/}
+            {/*                >*/}
+            {/*                    <div>*/}
+            {/*                        <h3 className="text-sm sm:text-base font-semibold mb-2">{item.newsName}</h3>*/}
+            {/*                        <p className="text-gray-500 text-xs sm:text-sm">{item.releaseTime}</p>*/}
+            {/*                    </div>*/}
+            {/*                    <Link href={`/news/${item.id}`}*/}
+            {/*                          className="text-blue-500 hover:underline mt-2 self-end text-xs sm:text-sm">*/}
+            {/*                        了解更多 &gt;*/}
+            {/*                    </Link>*/}
+            {/*                </div>*/}
+            {/*            ))}*/}
+            {/*        </div>*/}
+
+            {/*        /!* 分页器 *!/*/}
+                    <div className="flex justify-center mt-6">
+                        <PaginationClient
+                            current={pageNum}
+                            total={totalNews}
+                            pageSize={pageSize}
+                        />
+                    </div>
+            {/*    </div>*/}
+            {/*</AnimatedSection>*/}
+        </>
+    )
 }

+ 154 - 66
src/app/page.tsx

@@ -4,79 +4,167 @@ import MainTitle from "@/components/MainTitle";
 import FeatureCard from "@/components/FeatureCard";
 import ProductionSoft from "@/components/ProductionSoft";
 import ProductionHard from "@/components/ProductionHard";
+import PcoreProducts from "@/components/PcoreProducts";
 import {IntellectualPropertyStats} from "@/components/IntellectualPropertyStats";
 import ServerClient from "@/components/serverClient";
 import {serverGet} from "@/utils/request";
 import AnimatedSection from "@/components/AnimatedSection";
 
 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 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="slide" direction="left">
-        <ProductionSoft softIntroduction={basicInfo.softwareIntroduction || ""}/>
-      </AnimatedSection>
-
-      <AnimatedSection effect="slide" direction="right">
-        <ProductionHard hardIntroduction={basicInfo.hardwareIntroduction || ""} />
-      </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>
+    }
+
+    // 优化后的筛选函数:硬件产品中 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">
+                <PcoreProducts products={coreHardwareProducts}/>
+            </AnimatedSection>
+
+
+            {/*软件产品模块*/}
+            <AnimatedSection effect="slide" direction="left">
+                <ProductionSoft softIntroduction={basicInfo.softwareIntroduction || ""}/>
+            </AnimatedSection>
+            {/* 知识产权统计模块 */}
+            <AnimatedSection effect="scale">
+                <IntellectualPropertyStats/>
+            </AnimatedSection>
+            {/* 服务器客户端模块 */}
+            <AnimatedSection effect="scale">
+                <ServerClient/>
+            </AnimatedSection>
+        </>
+    )
 }

+ 129 - 0
src/app/products/[id]/head.css

@@ -0,0 +1,129 @@
+/* 容器整体样式 */
+.container {
+    display: flex;
+    align-items: center;
+    padding: 20px;
+    /*border: 1px solid #eee;*/
+    border-radius: 8px;
+    max-width: 1200px;
+    margin: 0 auto;
+    /*box-shadow: 0 2px 8px rgba(0,0,0,0.05);*/
+    /*justify-content: center;*/
+}
+
+/* 左侧区域 */
+.leftSection {
+    text-align: center;
+    padding-right: 20px;
+    font-size: 30px;
+    font-weight: bold;
+    width: 40%;
+}
+
+.title {
+    font-size: 22px;
+    font-weight: 700;
+    margin-bottom: 8px;
+    color: #333;
+}
+
+.subTitle {
+    font-size: 14px;
+    color: #666;
+    margin-bottom: 15px;
+}
+
+.highlight {
+    color: #e03131;
+    font-weight: 500;
+}
+
+.divider {
+    margin: 0 5px;
+    color: #999;
+}
+
+.deviceImg {
+    width: 100%;
+    height: 300px;
+    object-fit: contain;
+}
+
+/* 右侧区域 */
+.rightSection {
+    padding-left: 100px;
+    border-left: 1px solid #eee;
+}
+
+.productName {
+    font-size: 28px;
+    font-weight: 600;
+    color: #333;
+    margin-bottom: 4px;
+}
+
+.model {
+    font-size: 16px;
+    color: #666;
+    margin-bottom: 12px;
+}
+
+.featureList {
+    list-style: none;
+    padding: 0;
+    margin-bottom: 15px;
+    font-size: 15px;
+    color: #777777;
+    line-height: 1.8;
+}
+
+.specs {
+    font-size: 18px;
+    color: #555;
+    margin-bottom: 20px;
+    line-height: 1.6;
+}
+
+.label {
+    font-weight: 500;
+    color: #333;
+}
+
+/* 按钮组 */
+.buttonGroup {
+    display: flex;
+    gap: 15px;
+}
+
+.contactBtn {
+    display: inline-flex;
+    align-items: center;
+    gap: 5px;
+    padding: 8px 16px;
+    border: 1px solid blue;
+    border-radius: 4px;
+    color: blue;
+    text-decoration: none;
+    font-size: 14px;
+    transition: background-color 0.2s;
+}
+
+.contactBtn:hover {
+    background-color: blue;
+    color: white;
+}
+
+.quoteBtn {
+    padding: 8px 20px;
+    background-color: #e03131;
+    color: #fff;
+    border: none;
+    border-radius: 4px;
+    font-size: 14px;
+    cursor: pointer;
+    transition: background-color 0.2s;
+}
+
+.quoteBtn:hover {
+    background-color: #c92a2a;
+}

+ 95 - 51
src/app/products/[id]/page.tsx

@@ -4,65 +4,109 @@ import {serverGet} from "@/utils/request";
 import './rich.css'
 import MainTitle from "@/components/MainTitle";
 import ContentNotFound from "@/components/ContentNotFound";
-
+import './head.css'; // 引入样式文件
 export const dynamicParams = true
-
+const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL as string
 export const generateStaticParams = async () => {
-  const res = await serverGet<Page<ProductCenter>, { pageNum: number; pageSize: number }>(
-    "/webSite/getProductByPage",
-    {pageNum: 1, pageSize: 20}, {
-      next: {
-        revalidate: 1800
-      },
-      cache: "force-cache"
-    }
-  )
-  return res.data.records.map((item) => ({
-    id: item.id,
-  }))
+    const res = await serverGet<Page<ProductCenter>, { pageNum: number; pageSize: number }>(
+        "/webSite/getProductByPage",
+        {pageNum: 1, pageSize: 20}, {
+            next: {
+                revalidate: 1800
+            },
+            cache: "force-cache"
+        }
+    )
+    return res.data.records.map((item) => ({
+        id: item.id,
+    }))
 }
 
 async function Page({
-                      params,
+                        params,
                     }: {
-  params: Promise<{ id: string }>
+    params: Promise<{ id: string }>
 }) {
-  const {id} = await params;
-  const res = await serverGet<ProductCenter, { id: string }>(
-    "/webSite/getProductById",
-    {id}, {
-      next: {
-        revalidate: 1800
-      },
-      cache: "force-cache"
+    const {id} = await params;
+    const res = await serverGet<ProductCenter, { id: string }>(
+        "/webSite/getProductById",
+        {id}, {
+            next: {
+                revalidate: 1800
+            },
+            cache: "force-cache"
+        }
+    )
+    if (!res.data) {
+        return <ContentNotFound/>
     }
-  )
-  if (!res.data) {
-    return <ContentNotFound />
-  }
-  return (
-    <>
-      <div className="w-4/5 mx-auto">
-        <div className="pt-5 sm:pt-10 sm:ml-20 flex gap-2">
-          <span className="text-sm">您当前的所在位置:</span>
-          <Breadcrumb
-            separator=">"
-            items={[
-              {title: "产品列表", href: "/products"},
-              {title: res.data.productName || "产品详情"},
-            ]}
-          />
-        </div>
-      </div>
-      <div className="py-6 sm:py-10">
-        <MainTitle title={"产品详情"} titleLetter={"PRODUCT_DETAIL"}/>
-      </div>
-      <div
-        dangerouslySetInnerHTML={{__html: res.data.productDetails as string}}
-        className="ql-editor w-9/10 sm:w-7/10 mx-auto sm:px-20 sm:py-10"
-      />
-    </>
-  );
+    console.log(111,res.data)
+    return (
+        <>
+            <div className="w-4/5 mx-auto">
+                <div className="pt-5 sm:pt-10 sm:ml-20 flex gap-2">
+                    <span className="text-sm">您当前的所在位置:</span>
+                    <Breadcrumb
+                        separator=">"
+                        items={[
+                            {title: "产品列表", href: "/products"},
+                            {title: res.data.productName || "产品详情"},
+                        ]}
+                    />
+                </div>
+            </div>
+            <div className="py-6 sm:py-10">
+                <MainTitle title={"产品详情"} titleLetter={"PRODUCT_DETAIL"}/>
+            </div>
+            {/*头部区域*/}
+            <div className='container'>
+                {/* 左侧图片区域 */}
+                <div className='leftSection'>
+                    <img
+                        src={res.data.productUrl ? BASE_URL + res.data.productUrl : "/assets/home/20.png"} // 替换为实际设备图片链接
+                        alt="雷达水位监测仪EN200-D"
+                        className='deviceImg'
+                    />
+                </div>
+
+                {/* 右侧信息区域 */}
+                <div className='rightSection'>
+                    <h3 className='productName'>{res.data.productName}</h3>
+                    <p className='model'>{res.data.productModel}</p>
+                    <ul className='featureList'>
+                        {res.data.productIntroduction?.split('·').map((item, index) => (
+                            <li key={index}>{item.trim()}</li>
+                        ))}
+                    </ul>
+
+                    <div className='specs'>
+                        <p><span className='label'>型号:</span>{res.data.productModel}</p>
+                        <p><span className='label'>品牌:</span>中科盛阳</p>
+                        <p><span className='label'>场景:</span>{res.data.productScene}</p>
+                    </div>
+
+                    {/* 按钮区域 */}
+                    <div className='buttonGroup'>
+                        <a
+                            href="tel:0731-8531-5153"
+                            className='contactBtn'
+                        >
+                            <span className='phoneIcon'>📞</span> 0731-8531-5153
+                        </a>
+                        <button className='quoteBtn'>获取报价</button>
+                    </div>
+                </div>
+            </div>
+            <div className="ql-editor w-9/10 sm:w-7/10 mx-auto sm:px-20 sm:py-10">
+                <div className="text-2xl font-bold mb-4 border-b-2 border-blue-600 ql-color-blue">详细信息</div>
+            </div>
+
+            <div
+                dangerouslySetInnerHTML={{__html: res.data.productDetails as string}}
+                className="ql-editor w-9/10 sm:w-7/10 mx-auto sm:px-20 sm:py-10"
+            />
+        </>
+    );
 }
 
 

+ 81 - 41
src/app/products/page.tsx

@@ -3,49 +3,89 @@ import MainTitle from "@/components/MainTitle"
 import ProductMenu from "@/components/products/ProductMenuClient"
 import {serverGet} from "@/utils/request";
 import AnimatedSection from "@/components/AnimatedSection";
+
+import ProductionNew from "@/components/ProductionNew";
 import React, {Suspense} from "react";
 
+interface MenuItem {
+}
+
 export default async function ProductShowcase() {
 
-  const res = await serverGet<ProductCategory[]>("/webSite/getProductCategoryAndType",null,{
-    next: {
-      revalidate: 180
-    },
-    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 products = res.data.flatMap( (item) => item.productTypes.flatMap( (type) => type.productCenters))
-
-  return (
-    <>
-      <AnimatedSection effect="slide" direction="left">
-        <div>
-          <div className="w-full h-full flex items-center justify-center text-white text-4xl font-bold">
-            <Image src={"/assets/productions/1.png"} alt={"产品中心"} width={1920} height={1080}/>
-          </div>
-        </div>
-      </AnimatedSection>
-
-      <div className="py-6 sm:py-10">
-        <MainTitle title={"产品中心"}/>
-      </div>
-      <AnimatedSection effect="slide" direction="right">
-        <Suspense fallback={<div>加载中...</div>}>
-          <ProductMenu menuItems={menuItems} products={products} />
-        </Suspense>
-      </AnimatedSection>
-    </>
-  )
+    const res = await serverGet<ProductCategory[]>("/webSite/getProductCategoryAndType", null, {
+        next: {
+            revalidate: 180
+        },
+        cache: "force-cache"
+    })
+    if (res.code !== 200) {
+        return <div>服务器错误</div>
+    }
+    let hardwareProductList = []
+    for (let i = 0; i < res.data.length; i++) {
+        let a = res.data[i].productTypes.map((item) => ({
+            key: item.productTypeName,
+            label: item.productTypeName,
+            // children: item.productCenters.map((center) => ({
+            //     key: center.productCenterName,
+            //     label: center.productCenterName,
+            // }))
+        }))
+
+        hardwareProductList.push(...a);
+        hardwareProductList.reverse();
+        // if (res.data[i].productCategoryName === "硬件产品") {
+        //     hardwareProductList = res.data[i].productTypes.map((item) => ({
+        //         key: item.productTypeName,
+        //         label: item.productTypeName,
+        //         // children: item.productCenters.map((center) => ({
+        //         //     key: center.productCenterName,
+        //         //     label: center.productCenterName,
+        //         // }))
+        //     }))
+        // }else if(res.data[i].productCategoryName === "软件产品"){
+        //     softwareProductList = res.data[i].productTypes.map((item) => ({
+        //         key: item.productTypeName,
+        //         label: item.productTypeName,
+        //         // children: item.productCenters.map((center) => ({
+        //         //     key: center.productCenterName,
+        //         //     label: center.productCenterName,
+        //         // }))
+        //     }))
+        // }
+    }
+
+    const products = res.data.flatMap((item) => item.productTypes.flatMap((type) => type.productCenters))
+
+    return (
+        <>
+            <AnimatedSection effect="slide" direction="left">
+                <div>
+                    <div className="w-full h-full flex items-center justify-center text-white text-4xl font-bold">
+                        <Image src={"/assets/productions/1.png"} alt={"产品中心"} width={1920} height={1080}/>
+                    </div>
+                </div>
+            </AnimatedSection>
+
+            <div className="py-6 sm:py-10">
+                <MainTitle title={"产品中心"}/>
+            </div>
+
+            <AnimatedSection effect="slide" direction="right">
+                <Suspense fallback={<div>加载中...</div>}>
+                    <ProductMenu menuItems={hardwareProductList} products={products}/>
+                </Suspense>
+            </AnimatedSection>
+
+            {/*<AnimatedSection effect="slide" direction="right">*/}
+            {/*    <Suspense fallback={<div>加载中...</div>}>*/}
+            {/*        <ProductMenu menuItems={softwareProductList} products={products}/>*/}
+            {/*    </Suspense>*/}
+            {/*</AnimatedSection>*/}
+
+            {/*<AnimatedSection effect="slide" direction="right">*/}
+            {/*    <ProductionNew/>*/}
+            {/*</AnimatedSection>*/}
+        </>
+    )
 }

+ 3 - 3
src/components/IntellectualPropertyStats.tsx

@@ -26,9 +26,9 @@ export function IntellectualPropertyStats() {
 
   return (
     <>
-      <div className={"hidden sm:block w-4/5 mx-auto mt-10"}>
-        <SubTitle title="知识产权" />
-      </div>
+      {/*<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="w-full sm:w-7/12 mx-auto text-center">
           <div className="mb-8 sm:mb-16">

+ 21 - 21
src/components/PaginationClient.tsx

@@ -4,31 +4,31 @@ import {Pagination} from "antd"
 import {useRouter} from "next/navigation"
 
 interface PaginationClientProps {
-  current: number
-  total: number
-  pageSize: number
+    current: number
+    total: number
+    pageSize: number
 }
 
 export default function PaginationClient({
-                                           current,
-                                           total,
-                                           pageSize,
+                                             current,
+                                             total,
+                                             pageSize,
                                          }: PaginationClientProps) {
-  const router = useRouter()
+    const router = useRouter()
 
-  const handlePageChange = (page: number) => {
-    const url = new URL(window.location.href)
-    url.searchParams.set("page", page.toString())
-    router.push(url.pathname + url.search)
-  }
+    const handlePageChange = (page: number) => {
+        const url = new URL(window.location.href)
+        url.searchParams.set("page", page.toString())
+        router.push(url.pathname + url.search)
+    }
 
-  return (
-    <Pagination
-      current={current}
-      pageSize={pageSize}
-      total={total}
-      onChange={handlePageChange}
-      showSizeChanger={false}
-    />
-  )
+    return (
+        <Pagination
+            current={current}
+            pageSize={pageSize}
+            total={total}
+            onChange={handlePageChange}
+            showSizeChanger={false}
+        />
+    )
 }

+ 49 - 0
src/components/PcoreProducts.tsx

@@ -0,0 +1,49 @@
+'use client'
+import React from 'react';
+import Link from 'next/link';
+import Image from 'next/image';
+
+interface ProductMenuProps {
+    products: ProductItem[]
+}
+export default function CoreProducts({products}: ProductMenuProps) {
+    const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL as string
+    return (
+        <section className="w-full max-w-7xl mx-auto">
+            {/* 标题区域 */}
+            <div className="flex flex-col items-center mb-8 relative">
+                <Link
+                    href="/products"
+                    className="absolute right-0 top-0 text-sm text-blue-600 hover:underline flex items-center gap-1"
+                >更多
+                </Link>
+            </div>
+
+            {/* 产品列表 */}
+            <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
+                {products.map((product) => (
+                    <div
+                        className="border border-gray-200 rounded-sm p-6 flex flex-col items-center text-center hover:shadow-md transition-shadow duration-300"
+                    >
+                        <h3 className="font-bold text-lg mb-1 text-gray-800">{product.productName}</h3>
+                        <p className="text-sm mb-4 text-gray-500">
+                            <span className="text-red-600 line-clamp-1">{product.productIntroduction}</span>
+                        </p>
+                        {/* 产品图片 */}
+                        <div className="w-40 h-40 flex items-center justify-center mb-6">
+                            <Image
+                                src={product.productUrl ? BASE_URL + product.productUrl : "/assets/productions/2.png"}
+                                alt={product.productType}
+                                width={160}
+                                height={160}
+                                className="object-contain"
+                            />
+                        </div>
+                        <p className="text-sm text-gray-600">{product.productType}</p>
+                        <p className="text-xs text-gray-400 mt-1">{product.productModel}</p>
+                    </div>
+                ))}
+            </div>
+        </section>
+    );
+};

+ 181 - 0
src/components/ProductionNew.tsx

@@ -0,0 +1,181 @@
+'use client' // 新增这一行,标记为客户端组件
+import React, {useState} from 'react';
+import Image from 'next/image';
+import {useRouter} from "next/navigation";
+import Link from "next/link";
+import {ChevronRight} from "lucide-react";
+
+//树状结构的产品介绍图
+
+// 产品分类类型
+type Category = {
+    name: string;
+    isActive: boolean;
+};
+
+// 产品信息类型
+type Product = {
+    id: string;
+    title: string;
+    subTitle: string;
+    model: string;
+    imageUrl: string;
+};
+
+const WaterMonitorProducts: React.FC = () => {
+    // 分类列表数据
+    const [categories, setCategories] = useState<Category[]>([
+        {name: '水体监测', isActive: true},
+        {name: '结构监测', isActive: false},
+        {name: '气体监测', isActive: false},
+        {name: '智能传感', isActive: false},
+        {name: '压力监测', isActive: false},
+        {name: '软件平台', isActive: false},
+    ]);
+
+    // 水体监测产品列表
+    const waterProducts: Product[] = [
+        {
+            id: '1',
+            title: '雷达水位监测仪',
+            subTitle: '≥5年电池续航 · 0-40米水位量程',
+            model: 'EN200-D',
+            imageUrl: '/assets/home/19.png', // 替换为实际图片路径
+        },
+        {
+            id: '2',
+            title: '地埋式积水监测仪',
+            subTitle: '水位监测 · 水浸监测 · 漏电监测',
+            model: 'EN200-C',
+            imageUrl: '/assets/home/20.png', // 替换为实际图片路径
+        },
+        {
+            id: '3',
+            title: '雷达超声流量计',
+            subTitle: '雷达+超声双波测流',
+            model: 'EN202-RDU',
+            imageUrl: '/assets/home/21.png', // 替换为实际图片路径
+        },
+        {
+            id: '4',
+            title: '全光谱水质监测仪',
+            subTitle: '12种水质监测指标可选',
+            model: 'EN205-MWQ',
+            imageUrl: '/images/products/full-spectrum-water-quality.png',
+        },
+        {
+            id: '5',
+            title: '雷达电子水尺',
+            subTitle: '雷达水位监测 · 3种量程可选',
+            model: 'EN206-RLG',
+            imageUrl: '/images/products/radar-electronic-water-gauge.png',
+        },
+        {
+            id: '6',
+            title: '水位监测一体机',
+            subTitle: '4G通信 · 5年免维护 · IP68防护',
+            model: 'EN204-WP',
+            imageUrl: '/images/products/water-level-integrated.png',
+        },
+        {
+            id: '7',
+            title: '外夹式超声波流量计',
+            subTitle: '4G通信 · 5年免维护 · IP68防护',
+            model: 'EN203-DFM-S',
+            imageUrl: '/images/products/clamp-on-ultrasonic-flowmeter.png',
+        },
+        {
+            id: '8',
+            title: '雷达水位观测仪',
+            subTitle: '10m水位监测 · 400W像素',
+            model: 'EN200-D-V',
+            imageUrl: '/images/products/radar-level-observatory.png',
+        },
+    ];
+
+    // 切换分类
+    const handleCategoryChange = (index: number) => {
+        setCategories(
+            categories.map((item, i) => ({
+                ...item,
+                isActive: i === index,
+            }))
+        );
+    };
+    const router = useRouter()
+    const handlePageChange = () => {
+        router.push(`/products/${'04aa7cb7307771291d0ae12e7fb62b57'}`)
+    }
+
+    return (
+        <div className="flex flex-col md:flex-row w-full max-w-7xl mx-auto p-4 md:p-6">
+            {/* 侧边分类栏 */}
+            <div className="md:w-1/6 mb-6 md:mb-0">
+                <div className="flex flex-col">
+                    {categories.map((category, index) => (
+                        <button
+                            key={index}
+                            onClick={() => handleCategoryChange(index)}
+                            className={`
+                py-3 px-4 text-left
+                ${category.isActive
+                                ? 'bg-blue-600 text-white font-medium'
+                                : 'bg-white text-gray-700 hover:bg-gray-100'
+                            }
+                transition-colors duration-200
+              `}
+                        >
+                            {category.name}
+                        </button>
+                    ))}
+                </div>
+            </div>
+
+            {/* 产品列表区域 */}
+            <div className="md:w-5/6 md:pl-8">
+                <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
+                    {waterProducts.map((product) => (
+                        <div
+                            key={product.id}
+                            className="border border-gray-200 rounded-md p-4 hover:shadow-md transition-shadow duration-200"
+                        >
+                            <div className="flex flex-col items-center mb-3">
+                                {/* 产品图片 */}
+                                <div className="w-36 h-36 mb-3 flex items-center justify-center">
+                                    <Image
+                                        src={product.imageUrl}
+                                        alt={product.title}
+                                        width={144}
+                                        height={144}
+                                        className="object-contain"
+                                    />
+                                </div>
+                                {/* 产品标题 */}
+                                <h3 className="font-bold text-lg mb-1">{product.title}</h3>
+                                {/* 产品副标题 */}
+                                <p className="text-sm text-gray-500 mb-2">{product.subTitle}</p>
+                                {/* 产品型号 */}
+                                <p className="text-sm mb-3">{product.model}</p>
+                                {/* 获取报价按钮 */}
+                                <div className="w-full flex justify-center">
+                                    <button
+                                        className="bg-red-600 text-white px-4 py-1.5 rounded text-sm hover:bg-red-700 transition-colors duration-200"
+                                    >
+                                        获取报价
+                                    </button>
+                                    <button
+                                        onClick={() => handlePageChange()}
+                                        className="bg-blue-600 text-white px-4 py-1.5 rounded text-sm hover:bg-blue-700 transition-colors duration-200 ml-2">
+                                        产品详情
+                                    </button>
+                                </div>
+                            </div>
+                        </div>
+                    ))}
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default WaterMonitorProducts;

+ 1 - 1
src/components/ProductionSoft.tsx

@@ -57,7 +57,7 @@ function ProductionSoft({softIntroduction}: {softIntroduction: string}) {
         <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">软件简介</h2>
+            <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>

+ 60 - 51
src/components/products/ProductGridClient.tsx

@@ -1,65 +1,74 @@
 "use client"
 
-import {useState} from "react"
+import React, {useState} from "react"
 import Image from "next/image"
 import {Pagination} from "antd"
 import Link from "next/link";
 
 interface ProductGridProps {
-  products: ProductItem[]
+    products: ProductItem[]
 }
 
-export default function ProductGrid({ products }: ProductGridProps) {
-  const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL as string
+export default function ProductGrid({products}: ProductGridProps) {
+    const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL as string
+    // 分页控制
+    const [currentPage, setCurrentPage] = useState(1)
+    const pageSize = 6
+    // 当前页数据
+    const startIndex = (currentPage - 1) * pageSize
+    const currentProducts = products.slice(startIndex, startIndex + pageSize)
 
-  // 分页控制
-  const [currentPage, setCurrentPage] = useState(1)
-  const pageSize = 6
+    return (
+        <div className="flex flex-col gap-6">
+            {/* 产品网格 */}
+            <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
+                {currentProducts.map((product) => (
+                    <div
+                        key={product.productId}
+                        className="bg-white rounded-lg overflow-hidden shadow-lg hover:shadow-xl transition-all duration-300 hover:scale-[1.02]"
+                    >
+                        <div className="p-2">
+                            <Image
+                                className={"h-50 object-contain"}
+                                src={product.productUrl ? BASE_URL + product.productUrl : "/assets/productions/2.png"}
+                                alt={product.productName}
+                                width={1000}
+                                height={1000}
+                            />
+                        </div>
+                        <div className="p-4 sm:py-6 text-center">
+                            <h3 className="font-bold text-lg mb-1">{product.productName}</h3>
+                            {/* 产品描述*/}
+                            <p className="text-sm text-gray-500 mb-2 line-clamp-1">{product.productIntroduction}</p>
+                            {/* 产品型号 */}
+                            <p className="text-sm mb-3">{product.productModel}</p>
 
-  // 当前页数据
-  const startIndex = (currentPage - 1) * pageSize
-  const currentProducts = products.slice(startIndex, startIndex + pageSize)
-
-  return (
-    <div className="flex flex-col gap-6">
-      {/* 产品网格 */}
-      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
-        {currentProducts.map((product) => (
-          <div
-            key={product.productId}
-            className="bg-white rounded-lg overflow-hidden shadow-lg hover:shadow-xl transition-all duration-300 hover:scale-[1.02]"
-          >
-            <div className="p-2">
-              <Image
-                className={"h-60 object-contain"}
-                src={product.productUrl ? BASE_URL + product.productUrl : "/assets/productions/2.png"}
-                alt={product.productName}
-                width={1000}
-                height={1000}
-              />
-            </div>
-            <div className="p-4 sm:py-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.productName}
-              </h3>
-              <Link href={`/products/${product.productId}`} 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">
-                了解详情
-              </Link>
+                            <div className="w-full flex justify-center">
+                                <button
+                                    className="bg-red-600 text-white px-4 py-1.5 rounded text-sm hover:bg-red-700 transition-colors duration-200"
+                                >
+                                    获取报价
+                                </button>
+                                <Link href={`/products/${product.productId}`}
+                                      className="bg-blue-600 text-white px-4 py-1.5 rounded text-sm hover:bg-blue-700 transition-colors duration-200 ml-2">
+                                    了解详情
+                                </Link>
+                            </div>
+                        </div>
+                    </div>
+                ))}
             </div>
-          </div>
-        ))}
-      </div>
 
-      {/* 分页器 */}
-      <div className="flex justify-center">
-        <Pagination
-          current={currentPage}
-          pageSize={pageSize}
-          total={products.length}
-          onChange={(page) => setCurrentPage(page)}
-          showSizeChanger={false}
-        />
-      </div>
-    </div>
-  )
+            {/* 分页器 */}
+            <div className="flex justify-center">
+                <Pagination
+                    current={currentPage}
+                    pageSize={pageSize}
+                    total={products.length}
+                    onChange={(page) => setCurrentPage(page)}
+                    showSizeChanger={false}
+                />
+            </div>
+        </div>
+    )
 }

+ 252 - 251
src/components/products/ProductMenuClient.tsx

@@ -6,298 +6,299 @@ import ProductGrid from "@/components/products/ProductGridClient"
 import {useSearchParams} from "next/navigation";
 
 interface MenuItem {
-  key: string
-  label: string
-  children?: MenuItem[]
+    key: string
+    label: string
+    children?: MenuItem[]
 }
 
 interface ProductMenuProps {
-  menuItems: MenuItem[]
-  products: ProductItem[]
+    menuItems: MenuItem[]
+    products: ProductItem[]
 }
 
-export default function ProductMenu({ menuItems, products }: ProductMenuProps) {
-  // console.log(products);
-  const params = useSearchParams();
-  let baseSelect = '';
-  if(params.get('keyword')){
-    baseSelect = params.get('keyword') || '';
-    try {
-      if (Object.is(params.get('keyword'), "软件产品")) {
-        if (menuItems.filter((item) => Object.is(item.key, "软件产品")).length > 0) {
-          const list = menuItems.filter((item) => Object.is(item.key, "软件产品"));
-          baseSelect = list[0].children?.[0].key || '';
+export default function ProductMenu({menuItems, products}: ProductMenuProps) {
+    const params = useSearchParams();
+
+    let baseSelect = '';
+    if (params.get('keyword')) {
+        baseSelect = params.get('keyword') || '';
+        try {
+            if (Object.is(params.get('keyword'), "软件产品")) {
+                if (menuItems.filter((item) => Object.is(item.key, "软件产品")).length > 0) {
+                    const list = menuItems.filter((item) => Object.is(item.key, "软件产品"));
+                    baseSelect = list[0].children?.[0].key || '';
+                }
+            } else if (Object.is(params.get('keyword'), "硬件产品")) {
+                if (menuItems.filter((item) => Object.is(item.key, "硬件产品")).length > 0) {
+                    const list = menuItems.filter((item) => Object.is(item.key, "硬件产品"));
+                    baseSelect = list[0].children?.[0].key || '';
+                }
+            }
+        } catch (e) {
+            console.log(e, "没有匹配到对应的菜单类型")
+            baseSelect = menuItems[0].children?.[0].key || '';
         }
-      } else if (Object.is(params.get('keyword'), "硬件产品")) {
-        if (menuItems.filter((item) => Object.is(item.key, "硬件产品")).length > 0) {
-          const list = menuItems.filter((item) => Object.is(item.key, "硬件产品"));
-          baseSelect = list[0].children?.[0].key || '';
+    } else if (menuItems[0]?.children) {
+        if (menuItems[0]?.children?.length > 0) {
+            baseSelect = menuItems[0].children?.[0].key || '';
         }
-      }
-    } catch (e) {
-      console.log(e,"没有匹配到对应的菜单类型")
-      baseSelect = menuItems[0].children?.[0].key || '';
-    }
-  }else if(menuItems[0]?.children){
-    if(menuItems[0]?.children?.length>0){
-      baseSelect = menuItems[0].children?.[0].key || '';
+    } else {
+        baseSelect = menuItems[0]?.key || '';
     }
-  }else {
-    baseSelect = menuItems[0]?.key || '';
-  }
-  const [selectedKey, setSelectedKey] = useState(baseSelect)
-  const [expandedKeys, setExpandedKeys] = useState<string[]>([])
-  const showProductList = useMemo(
-    () => products.filter((product) => product.productType === selectedKey),
-    [products, selectedKey]
-  );
+    const [selectedKey, setSelectedKey] = useState(baseSelect)
+    const [expandedKeys, setExpandedKeys] = useState<string[]>([])
+    const showProductList = useMemo(
+        () => products.filter((product) => product.productType === selectedKey),
+        [products, selectedKey]
+    );
 
-  useEffect(() => {
-    if(window.innerWidth > 640){
-      for (const item of menuItems) {
-        if (item.children) {
-          const child = item.children.find((child) => child.key === baseSelect)
-          if (child) {
-            // console.log(child)
-            setExpandedKeys([item.key])
-            break
-          }
+    useEffect(() => {
+        if (window.innerWidth > 640) {
+            for (const item of menuItems) {
+                if (item.children) {
+                    const child = item.children.find((child) => child.key === baseSelect)
+                    if (child) {
+                        // console.log(child)
+                        setExpandedKeys([item.key])
+                        break
+                    }
+                }
+            }
         }
-      }
-    }
-  }, [menuItems, baseSelect])
+    }, [menuItems, baseSelect])
 
-  const findLabelByKey = (key: string): string => {
-    for (const item of menuItems) {
-      if (item.key === key) {
-        return item.label
-      }
+    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
+            if (item.children) {
+                const child = item.children.find((child) => child.key === key)
+                if (child) {
+                    return child.label
+                }
+            }
         }
-      }
-    }
 
-    return key
-  }
+        return key
+    }
 
-  const handleMenuClick = (key: string) => {
-    setSelectedKey(key)
-    const urlSearchParams = new URLSearchParams(params.toString());
-    urlSearchParams.set('keyword', key);
-    window.history.replaceState({}, '', `?${urlSearchParams.toString()}`);
+    const handleMenuClick = (key: string) => {
+        setSelectedKey(key)
+        const urlSearchParams = new URLSearchParams(params.toString());
+        urlSearchParams.set('keyword', key);
+        window.history.replaceState({}, '', `?${urlSearchParams.toString()}`);
 
-    // 查找包含这个子菜单的父菜单并自动展开
-    for (const item of menuItems) {
-      if (item.children) {
-        const child = item.children.find((child) => child.key === key)
-        if (child) {
-          // 如果找到子菜单,自动展开父菜单
-          setExpandedKeys((prev) => {
-            if (prev.includes(item.key)) {
-              return prev
+        // 查找包含这个子菜单的父菜单并自动展开
+        for (const item of menuItems) {
+            if (item.children) {
+                const child = item.children.find((child) => child.key === key)
+                if (child) {
+                    // 如果找到子菜单,自动展开父菜单
+                    setExpandedKeys((prev) => {
+                        if (prev.includes(item.key)) {
+                            return prev
+                        }
+                        return [...prev, item.key]
+                    })
+                    break
+                }
             }
-            return [...prev, item.key]
-          })
-          break
         }
-      }
     }
-  }
 
-  const toggleExpanded = (key: string) => {
-    setExpandedKeys((prev) => (prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]))
-  }
+    const toggleExpanded = (key: string) => {
+        setExpandedKeys((prev) => (prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]))
+    }
 
-  return (
-    <>
-      <div className="sm:hidden">
-        <MobileMenu
-          menuItems={menuItems}
-          selectedKey={selectedKey}
-          expandedKeys={expandedKeys}
-          onMenuClick={handleMenuClick}
-          onToggleExpanded={toggleExpanded}
-        />
-      </div>
+    return (
+        <>
+            <div className="sm:hidden">
+                <MobileMenu
+                    menuItems={menuItems}
+                    selectedKey={selectedKey}
+                    expandedKeys={expandedKeys}
+                    onMenuClick={handleMenuClick}
+                    onToggleExpanded={toggleExpanded}
+                />
+            </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
-            menuItems={menuItems}
-            selectedKey={selectedKey}
-            expandedKeys={expandedKeys}
-            onMenuClick={handleMenuClick}
-            onToggleExpanded={toggleExpanded}
-          />
-        </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
+                        menuItems={menuItems}
+                        selectedKey={selectedKey}
+                        expandedKeys={expandedKeys}
+                        onMenuClick={handleMenuClick}
+                        onToggleExpanded={toggleExpanded}
+                    />
+                </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>
-          <ProductGrid products={showProductList} />
-        </div>
-      </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>
+                    <ProductGrid products={showProductList}/>
+                </div>
+            </div>
+        </>
+    )
 }
 
 function DesktopMenu({
-                       menuItems,
-                       selectedKey,
-                       expandedKeys,
-                       onMenuClick,
-                       onToggleExpanded,
+                         menuItems,
+                         selectedKey,
+                         expandedKeys,
+                         onMenuClick,
+                         onToggleExpanded,
                      }: {
-  menuItems: MenuItem[]
-  selectedKey: string
-  expandedKeys: string[]
-  onMenuClick: (key: string) => void
-  onToggleExpanded: (key: string) => void
+    menuItems: MenuItem[]
+    selectedKey: string
+    expandedKeys: string[]
+    onMenuClick: (key: string) => void
+    onToggleExpanded: (key: string) => void
 }) {
-  return (
-    <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 ||
-              (item.children && item.children.some((child) => selectedKey === child.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) {
-                onToggleExpanded(item.key)
-              } else {
-                onMenuClick(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 ||
-                  (item.children && item.children.some((child) => selectedKey === child.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>
+    return (
+        <div className="space-y-1">
+            {menuItems.map((item) => (
+                <div key={item.key}>
+                    <div
+                        className={`flex items-center justify-between px-4 py-4 text-sm font-medium cursor-pointer rounded-lg transition-all duration-300 ${
+                            selectedKey === item.key ||
+                            (item.children && item.children.some((child) => selectedKey === child.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) {
+                                onToggleExpanded(item.key)
+                            } else {
+                                onMenuClick(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 ||
+                                    (item.children && item.children.some((child) => selectedKey === child.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={() => onMenuClick(child.key)}
-                >
-                  <span className="flex items-center">{child.label}</span>
+                    {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={() => onMenuClick(child.key)}
+                                >
+                                    <span className="flex items-center">{child.label}</span>
+                                </div>
+                            ))}
+                        </div>
+                    )}
                 </div>
-              ))}
-            </div>
-          )}
+            ))}
         </div>
-      ))}
-    </div>
-  )
+    )
 }
 
 function MobileMenu({
-                      menuItems,
-                      selectedKey,
-                      expandedKeys,
-                      onMenuClick,
-                      onToggleExpanded,
+                        menuItems,
+                        selectedKey,
+                        expandedKeys,
+                        onMenuClick,
+                        onToggleExpanded,
                     }: {
-  menuItems: MenuItem[]
-  selectedKey: string
-  expandedKeys: string[]
-  onMenuClick: (key: string) => void
-  onToggleExpanded: (key: string) => void
+    menuItems: MenuItem[]
+    selectedKey: string
+    expandedKeys: string[]
+    onMenuClick: (key: string) => void
+    onToggleExpanded: (key: string) => void
 }) {
-  return (
-    <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) {
-                    onToggleExpanded(item.key)
-                  } else {
-                    onMenuClick(item.key)
-                  }
-                }}
-              >
+    return (
+        <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) {
+                                        onToggleExpanded(item.key)
+                                    } else {
+                                        onMenuClick(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>
-                  )}
+                    {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>
+                            </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={() => {
-                        onMenuClick(child.key)
-                        onToggleExpanded(item.key) // Close dropdown after selection
-                      }}
-                    >
-                      {child.label}
-                    </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={() => {
+                                                onMenuClick(child.key)
+                                                onToggleExpanded(item.key) // Close dropdown after selection
+                                            }}
+                                        >
+                                            {child.label}
+                                        </button>
+                                    ))}
+                                </div>
+                            )}
+                        </div>
+                    ))}
                 </div>
-              )}
             </div>
-          ))}
         </div>
-      </div>
-    </div>
-  )
+    )
 }

+ 220 - 204
src/types/index.ts

@@ -1,248 +1,264 @@
 declare interface Window {
-  init: any
-  BMapGL: any
+    init: any
+    BMapGL: any
 }
+
 interface Feature {
-  img: string
-  title: string
-  subtitle: string
-  href: string
+    img: string
+    title: string
+    subtitle: string
+    href: string
 }
-interface Page<T>{
-  records: T[];
-  total: number;
-  size: number;
-  current: number;
+
+interface Page<T> {
+    records: T[];
+    total: number;
+    size: number;
+    current: number;
 }
+
 interface RecruitmentInfo {
-  id: string;
-  jobOpenings: string;        // 职位名称
-  keyTerms: string;           // 关键词
-  jobRequirements: string;    // 职位要求
-  numberRecruits: number;     // 招聘人数 (改为数字类型)
-  salary: string;             // 薪资
-  workLocation: string;       // 工作地点
-  contact: string;            // 联系人
-  contactInformation: string; // 联系方式
-  remarks: string;            // 备注
-  createBy: string | null;    // 创建人
-  createTime: string;         // 创建时间 (ISO 8601 格式)
-  updateBy: string | null;    // 更新人
-  updateTime: string;         // 更新时间 (ISO 8601 格式)
+    id: string;
+    jobOpenings: string;        // 职位名称
+    keyTerms: string;           // 关键词
+    jobRequirements: string;    // 职位要求
+    numberRecruits: number;     // 招聘人数 (改为数字类型)
+    salary: string;             // 薪资
+    workLocation: string;       // 工作地点
+    contact: string;            // 联系人
+    contactInformation: string; // 联系方式
+    remarks: string;            // 备注
+    createBy: string | null;    // 创建人
+    createTime: string;         // 创建时间 (ISO 8601 格式)
+    updateBy: string | null;    // 更新人
+    updateTime: string;         // 更新时间 (ISO 8601 格式)
 }
 
 interface HonorInfo {
-  /**
-   * 荣誉资质ID
-   */
-  id?: string;
-
-  /**
-   * 证书类型
-   */
-  certificateType?: string;
-
-  /**
-   * 文件列表
-   */
-  fileList?: {id: string; url: string;}[];
+    /**
+     * 荣誉资质ID
+     */
+    id?: string;
+
+    /**
+     * 证书类型
+     */
+    certificateType?: string;
+
+    /**
+     * 文件列表
+     */
+    fileList?: { id: string; url: string; }[];
 }
 
 /**
  * 首页基础信息
  */
 interface BasicInfo {
-  /**
-   * 主键
-   */
-  id?: string;
-
-  /**
-   * 公司简介
-   */
-  companyProfile?: string;
-
-  /**
-   * 公司简介2
-   */
-  companyProfileTwo?: string;
-
-  /**
-   * 公司简介图片地址
-   */
-  companyProfileUrl?: string;
-
-  /**
-   * 软件简介
-   */
-  softwareIntroduction?: string;
-
-  /**
-   * 硬件简介
-   */
-  hardwareIntroduction?: string;
-
-  /**
-   * 电话
-   */
-  telephone?: string;
-
-  /**
-   * 服务热线
-   */
-  serviceHotline?: string;
-
-  /**
-   * 咨询热线
-   */
-  consultationHotline?: string;
-
-  /**
-   * 邮箱
-   */
-  email?: string;
-
-  /**
-   * 地址
-   */
-  address?: string;
-
-  /**
-   * 二维码图片地址
-   */
-  qrCodeUrl?: string;
+    /**
+     * 主键
+     */
+    id?: string;
+
+    /**
+     * 公司简介
+     */
+    companyProfile?: string;
+
+    /**
+     * 公司简介2
+     */
+    companyProfileTwo?: string;
+
+    /**
+     * 公司简介图片地址
+     */
+    companyProfileUrl?: string;
+
+    /**
+     * 软件简介
+     */
+    softwareIntroduction?: string;
+
+    /**
+     * 硬件简介
+     */
+    hardwareIntroduction?: string;
+
+    /**
+     * 电话
+     */
+    telephone?: string;
+
+    /**
+     * 服务热线
+     */
+    serviceHotline?: string;
+
+    /**
+     * 咨询热线
+     */
+    consultationHotline?: string;
+
+    /**
+     * 邮箱
+     */
+    email?: string;
+
+    /**
+     * 地址
+     */
+    address?: string;
+
+    /**
+     * 二维码图片地址
+     */
+    qrCodeUrl?: string;
 }
 
 interface NewsUpdates {
-  /**
-   * 主键
-   */
-  id?: string;
-
-  /**
-   * 新闻名称
-   */
-  newsName?: string;
-
-  /**
-   * 新闻详情
-   */
-  newsDetails?: string;
-
-  /**
-   * 新闻图片地址
-   */
-  newsUrl: string;
-
-  /**
-   * 发布时间
-   */
-  releaseTime?: string;
-
-  /**
-   * 是否为特别新闻
-   */
-  isSpecial?: boolean;
+    /**
+     * 主键
+     */
+    id?: string;
+
+    /**
+     * 新闻名称
+     */
+    newsName?: string;
+
+    /**
+     * 新闻详情
+     */
+    newsDetails?: string;
+
+    /**
+     * 新闻图片地址
+     */
+    newsUrl: string;
+
+    /**
+     * 发布时间
+     */
+    releaseTime?: string;
+
+    /**
+     * 是否为特别新闻
+     */
+    isSpecial?: boolean;
 }
 
 interface Solution {
-  /**
-   * 主键
-   */
-  id?: string;
-
-  /**
-   * 方案名称
-   */
-  programName?: string;
-
-  /**
-   * 发布时间
-   */
-  releaseTime?: string;
-  /**
-   * 方案详情
-   */
-  programDetails?: string;
-
-  /**
-   * 方案图片地址
-   */
-  productUrl?: string;
-
-  /**
-   * 创建人
-   */
-  createBy?: string;
-
-  /**
-   * 创建时间
-   */
-  createTime?: string;
-
-  /**
-   * 修改人
-   */
-  updateBy?: string;
-
-  /**
-   * 修改时间
-   */
-  updateTime?: string;
+    /**
+     * 主键
+     */
+    id?: string;
+
+    /**
+     * 方案名称
+     */
+    programName?: string;
+
+    /**
+     * 发布时间
+     */
+    releaseTime?: string;
+    /**
+     * 方案详情
+     */
+    programDetails?: string;
+
+    /**
+     * 方案图片地址
+     */
+    productUrl?: string;
+
+    /**
+     * 创建人
+     */
+    createBy?: string;
+
+    /**
+     * 创建时间
+     */
+    createTime?: string;
+
+    /**
+     * 修改人
+     */
+    updateBy?: string;
+
+    /**
+     * 修改时间
+     */
+    updateTime?: string;
 }
+
 // 产品详细信息接口
 interface ProductItem {
-  // 产品唯一标识符
-  productId: string;
-  // 产品名称
-  productName: string;
-  // 产品分类
-  productCategory: string;
-  // 产品类型
-  productType: string;
-  // 产品图片URL地址
-  productUrl: string | null;
+    // 产品唯一标识符
+    isCore: string;
+    productId: string;
+    // 产品名称
+    productName: string;
+    // 产品分类
+    productCategory: string;
+    // 产品类型
+    productType: string;
+    // 产品图片URL地址
+    productUrl: string | null;
+    // 产品介绍
+    productIntroduction: string;
+    // 产品型号
+    productModel: string;
 }
 
 // 产品类型接口
 interface ProductType {
-  // 产品类型名称
-  productTypeName: string;
-  // 该类型下的产品列表
-  productCenters: ProductItem[];
+    // 产品类型名称
+    productTypeName: string;
+    // 该类型下的产品列表
+    productCenters: ProductItem[];
 }
 
 // 产品分类接口
 interface ProductCategory {
-  // 产品分类名称
-  productCategoryName: string;
-  // 该分类下的产品类型列表
-  productTypes: ProductType[];
+    // 产品分类名称
+    productCategoryName: string;
+    // 该分类下的产品类型列表
+    productTypes: ProductType[];
 }
 
 interface ProductCenter {
-  // 主键
-  id?: string;
+    // 主键
+    productScene: any;
+    id?: string;
+
+    // 产品分类
+    productCategory?: string;
+
+    // 产品类型
+    productType?: string;
 
-  // 产品分类
-  productCategory?: string;
+    // 产品名称
+    productName?: string;
 
-  // 产品类型
-  productType?: string;
+    // 产品详情
+    productDetails?: string;
 
-  // 产品名称
-  productName?: string;
+    // 产品图片地址
+    productUrl?: string;
 
-  // 产品详情
-  productDetails?: string;
+    // 创建人
+    createBy?: string;
 
-  // 产品图片地址
-  productUrl?: string;
+    // 修改人
+    updateBy?: string;
 
-  // 创建人
-  createBy?: string;
+    // 产品介绍
+    productIntroduction?: string;
 
-  // 修改人
-  updateBy?: string;
+    // 产品型号
+    productModel?: string;
 
 }

+ 19 - 5
tsconfig.json

@@ -1,7 +1,11 @@
 {
   "compilerOptions": {
     "target": "ES5",
-    "lib": ["dom", "dom.iterable", "esnext"],
+    "lib": [
+      "dom",
+      "dom.iterable",
+      "esnext"
+    ],
     "allowJs": true,
     "skipLibCheck": true,
     "strict": true,
@@ -11,7 +15,7 @@
     "moduleResolution": "bundler",
     "resolveJsonModule": true,
     "isolatedModules": true,
-    "jsx": "preserve",
+    "jsx": "react-jsx",
     "incremental": true,
     "plugins": [
       {
@@ -19,9 +23,19 @@
       }
     ],
     "paths": {
-      "@/*": ["./src/*"]
+      "@/*": [
+        "./src/*"
+      ]
     }
   },
-  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
-  "exclude": ["node_modules"]
+  "include": [
+    "next-env.d.ts",
+    "**/*.ts",
+    "**/*.tsx",
+    ".next/types/**/*.ts",
+    ".next/dev/types/**/*.ts"
+  ],
+  "exclude": [
+    "node_modules"
+  ]
 }

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません