Browse Source

跟新留言板功能

丁烨烨 4 months ago
parent
commit
ad69964f22

+ 1 - 1
src/app/page.tsx

@@ -114,7 +114,7 @@ export default async function Home() {
         <div className='my-6 sm:mt-10'>
           <MainTitle title="荣誉资质" titleLetter="HONOR"/>
         </div>
-        <div className={"scroll-mt-10 max-w-4/5 mx-auto py-12 px-4"}>
+        <div className={"scroll-mt-10 max-w-4/5 mx-auto px-4"}>
           <Honor honorList={honorRes.data}/>
         </div>
       </AnimatedSection>

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

@@ -106,11 +106,11 @@ async function Page({
                             <span>📞</span>
                             0731-8531-5153
                         </a>
-                        <button
-                            className="px-5 py-2 bg-red-600 hover:bg-red-700 text-white rounded text-sm font-medium transition-colors"
-                        >
-                            获取报价
-                        </button>
+                        {/*<button*/}
+                        {/*    className="px-5 py-2 bg-red-600 hover:bg-red-700 text-white rounded text-sm font-medium transition-colors"*/}
+                        {/*>*/}
+                        {/*    获取报价*/}
+                        {/*</button>*/}
                     </div>
                 </div>
             </div>

+ 1 - 1
src/components/CoreProducts.tsx

@@ -59,7 +59,7 @@ export default function CoreProducts({products}: ProductMenuProps) {
   }, [orderedProducts, activeTab])
 
   return (
-    <section className="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 min-h-180">
+    <section className="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
 
       <div className="w-full border border-gray-300 rounded-md overflow-hidden">
         <div className="flex overflow-x-auto">

+ 2 - 2
src/components/IntellectualPropertyStats.tsx

@@ -27,9 +27,9 @@ export function IntellectualPropertyStats() {
       {/*<div className={"hidden sm:block w-4/5 mx-auto mt-10"}>*/}
       {/*  <SubTitle title="知识产权" />*/}
       {/*</div>*/}
-      <div className="sm:mt-12 py-8 sm:py-20 px-4 bg-[url('/assets/home/23.png')] bg-cover flex items-center justify-center">
+      <div className="sm:mt-6 py-8 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">
+          <div className="mb-8">
             <h2 className="text-xl sm:text-2xl font-bold text-[#0f397b] mb-2 text-balance shuheiti">知识产权</h2>
             <p className="text-xs sm:text-sm tracking-widest text-gray-600 uppercase font-medium shuheiti opacity-45">
               COMPANY PROPERTY RIGHTS

+ 80 - 77
src/components/news/NewsList.tsx

@@ -42,45 +42,45 @@ const NewsItem: React.FC<NewsItemProps> = ({news: article}) => {
   const coverImageUrl = article.newsUrl ? `${BASE_URL}${article.newsUrl}` : '';
 
   return (
-    <div
-      className="group flex flex-col md:flex-row md:items-start bg-white rounded-xl p-[15px] md:p-5 shadow-[0_4px_15px_rgba(0,0,0,0.05)] hover:shadow-[0_8px_25px_rgba(0,0,0,0.1)] transition-[transform,box-shadow] duration-300 ease-in-out gap-[15px] md:gap-5 hover:-translate-y-[3px]"
-    >
-      <div className="shrink-0 w-full h-[180px] md:w-[150px] md:h-[100px] rounded-lg overflow-hidden">
-        <Image
-          width={1000}
-          height={1000}
-          src={coverImageUrl}
-          alt={article.newsName} // 补充有意义的alt属性,优化可访问性
-          loading="lazy"
-          className="w-full h-full object-cover transition-transform duration-300 ease-in-out group-hover:scale-[1.05]"
-          onError={(e) => {
-            // 图片加载失败兜底,提升用户体验
-            (e.target as HTMLImageElement).src = '/default-news-cover.png';
-          }}
-        />
-      </div>
-      <div className="flex-1 overflow-hidden">
-        <h3
-          className="text-[1.1rem] md:text-[1.2rem] text-gray-800 font-semibold mb-[10px] leading-snug line-clamp-2 md:line-clamp-1">
-          {article.newsName}
-        </h3>
-        <div className="flex items-center justify-between mb-2 text-[0.85rem] text-gray-400">
-          <span className="md:mx-5">{article.newsDesc}</span>
-          {/* 可选字段兜底,避免空值展示 */}
-          <span className="whitespace-nowrap">作者:{article.newsAuthor || '未知'}</span>
+      <div
+          className="group flex flex-col md:flex-row md:items-start bg-white rounded-xl p-[15px] md:p-5 shadow-[0_4px_15px_rgba(0,0,0,0.05)] hover:shadow-[0_8px_25px_rgba(0,0,0,0.1)] transition-[transform,box-shadow] duration-300 ease-in-out gap-[15px] md:gap-5 hover:-translate-y-[3px]"
+      >
+        <div className="shrink-0 w-full h-[180px] md:w-[150px] md:h-[100px] rounded-lg overflow-hidden">
+          <Image
+              width={1000}
+              height={1000}
+              src={coverImageUrl}
+              alt={article.newsName} // 补充有意义的alt属性,优化可访问性
+              loading="lazy"
+              className="w-full h-full object-cover transition-transform duration-300 ease-in-out group-hover:scale-[1.05]"
+              onError={(e) => {
+                // 图片加载失败兜底,提升用户体验
+                (e.target as HTMLImageElement).src = '/default-news-cover.png';
+              }}
+          />
+        </div>
+        <div className="flex-1 overflow-hidden">
+          <h3
+              className="text-[1.1rem] md:text-[1.2rem] text-gray-800 font-semibold mb-[10px] leading-snug line-clamp-2 md:line-clamp-1">
+            {article.newsName}
+          </h3>
+          <div className="flex items-center justify-between mb-2 text-[0.85rem] text-gray-400">
+            <span className="md:mx-5">{article.newsDesc}</span>
+            {/* 可选字段兜底,避免空值展示 */}
+            <span className="whitespace-nowrap">作者:{article.newsAuthor || '未知'}</span>
+          </div>
+          <p className="text-[0.9rem] md:text-[0.95rem] text-gray-600 leading-relaxed line-clamp-3 md:line-clamp-2">
+            {article.content}
+          </p>
         </div>
-        <p className="text-[0.9rem] md:text-[0.95rem] text-gray-600 leading-relaxed line-clamp-3 md:line-clamp-2">
-          {article.content}
-        </p>
+        <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>
-      <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>
   );
 };
 
@@ -92,14 +92,14 @@ const NewsList: React.FC<NewsListProps> = ({newsList}) => {
   }
 
   return (
-    <div className="flex flex-col gap-5 mt-5 mb-10">
-      {newsList.map((news) => (
-        <NewsItem
-          key={news.id}
-          news={news}
-        />
-      ))}
-    </div>
+      <div className="flex flex-col gap-5 mt-5 mb-10">
+        {newsList.map((news) => (
+            <NewsItem
+                key={news.id}
+                news={news}
+            />
+        ))}
+      </div>
   );
 };
 
@@ -107,8 +107,8 @@ const NewsList: React.FC<NewsListProps> = ({newsList}) => {
 const NewsPage: React.FC<NewsPageProps> = ({newsList = []}) => {
   // 类型守卫,确保newsList是数组类型
   const safeNewsList = useMemo(() =>
-      Array.isArray(newsList) ? newsList : [],
-    [newsList]);
+          Array.isArray(newsList) ? newsList : [],
+      [newsList]);
 
   const [activeTab, setActiveTab] = useState<keyof typeof CATEGORY_MAP>('company'); // 严格类型约束
   const [filteredNews, setFilteredNews] = useState<Article[]>([]);
@@ -121,7 +121,7 @@ const NewsPage: React.FC<NewsPageProps> = ({newsList = []}) => {
     const timer = setTimeout(() => {
       const targetCategory = CATEGORY_MAP[activeTab];
       const filtered = safeNewsList.filter(
-        (news) => news.newsCategory === targetCategory
+          (news) => news.newsCategory === targetCategory
       );
 
       setFilteredNews(filtered);
@@ -138,36 +138,39 @@ const NewsPage: React.FC<NewsPageProps> = ({newsList = []}) => {
   };
 
   return (
-    <div className="max-w-[1200px] mx-auto min-h-screen px-4">
-      <div className="flex justify-center mb-[30px] gap-[10px]">
-        <button
-          className={`px-[30px] py-3 rounded-full text-base transition-all duration-300 ease-in-out outline-none ${
-            activeTab === 'company'
-              ? "bg-[#2f54eb] text-white shadow-[0_4px_12px_rgba(47,84,235,0.3)]"
-              : "bg-gray-200 text-gray-500 hover:bg-gray-300 hover:text-gray-800"
-          }`}
-          onClick={() => handleTabChange('company')}
-          aria-pressed={activeTab === 'company'} // 优化可访问性
-        >
-          公司新闻
-        </button>
-        <button
-          className={`px-[30px] py-3 rounded-full text-base transition-all duration-300 ease-in-out outline-none ${
-            activeTab === 'industry'
-              ? "bg-[#2f54eb] text-white shadow-[0_4px_12px_rgba(47,84,235,0.3)]"
-              : "bg-gray-200 text-gray-500 hover:bg-gray-300 hover:text-gray-800"
-          }`}
-          onClick={() => handleTabChange('industry')}
-          aria-pressed={activeTab === 'industry'}
-        >
-          行内新闻
-        </button>
-      </div>
+      <div className="max-w-[1200px] mx-auto min-h-screen px-4">
+        <div className="flex justify-center mb-[30px] gap-[10px]">
+          {/* 核心修复:提升文字样式优先级 + 调整类名顺序 */}
+          <button
+              className={`px-[30px] py-3 rounded-full transition-all duration-300 ease-in-out outline-none font-medium
+            ${activeTab === 'company'
+                  ? 'bg-[#2f54eb] text-white !text-white shadow-[0_4px_12px_rgba(47,84,235,0.3)]'
+                  : 'bg-gray-200 text-gray-500 hover:bg-gray-300 hover:text-gray-800'
+              }`}
+              onClick={() => handleTabChange('company')}
+              aria-pressed={activeTab === 'company'}
+              style={{ color: activeTab === 'company' ? 'white' : '' }} // 内联样式兜底,确保优先级
+          >
+            公司新闻
+          </button>
+          <button
+              className={`px-[30px] py-3 rounded-full transition-all duration-300 ease-in-out outline-none font-medium
+            ${activeTab === 'industry'
+                  ? 'bg-[#2f54eb] text-white !text-white shadow-[0_4px_12px_rgba(47,84,235,0.3)]'
+                  : 'bg-gray-200 text-gray-500 hover:bg-gray-300 hover:text-gray-800'
+              }`}
+              onClick={() => handleTabChange('industry')}
+              aria-pressed={activeTab === 'industry'}
+              style={{ color: activeTab === 'industry' ? 'white' : '' }} // 内联样式兜底
+          >
+            行内新闻
+          </button>
+        </div>
 
-      {isLoading && <div className="text-center text-[1.2rem] text-gray-500 py-10">加载中...</div>}
-      {!isLoading && <NewsList newsList={filteredNews}/>}
-    </div>
+        {isLoading && <div className="text-center text-[1.2rem] text-gray-500 py-10">加载中...</div>}
+        {!isLoading && <NewsList newsList={filteredNews}/>}
+      </div>
   );
 };
 
-export default NewsPage;
+export default NewsPage;

+ 168 - 7
src/components/products/ProductGridClient.tsx

@@ -2,8 +2,27 @@
 
 import React, {useState} from "react"
 import Image from "next/image"
-import {Pagination} from "antd"
+import {Pagination, Modal, Form, Input, Button, message} from "antd"
 import Link from "next/link";
+import {serverPost} from "@/utils/request";
+
+// 定义产品项接口
+interface ProductItem {
+    productId: string | number;
+    productName: string;
+    productUrl?: string;
+    productIntroduction: string;
+    productModel: string;
+}
+
+// 留言表单数据接口(移除quantity字段)
+interface QuoteFormValues {
+    name: string;
+    phone: string;
+    message: string;
+    productId: string | number;
+    productName: string;
+}
 
 interface ProductGridProps {
     products: ProductItem[]
@@ -11,13 +30,79 @@ interface ProductGridProps {
 
 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 [isModalOpen, setIsModalOpen] = useState(false)
+    const [currentProduct, setCurrentProduct] = useState<ProductItem | null>(null)
+    const [form] = Form.useForm()
+    // 添加加载状态,防止重复提交
+    const [submitting, setSubmitting] = useState(false)
+
     // 当前页数据
     const startIndex = (currentPage - 1) * pageSize
     const currentProducts = products.slice(startIndex, startIndex + pageSize)
 
+    // 打开弹窗并记录当前产品(移除quantity初始化)
+    const showModal = (product: ProductItem) => {
+        setCurrentProduct(product)
+        setIsModalOpen(true)
+        // 延迟设置表单值,确保Modal渲染完成后再初始化Form
+        setTimeout(() => {
+            form.setFieldsValue({
+                productId: product.productId,
+                productName: product.productName
+                // 移除quantity相关初始化
+            })
+        }, 0)
+    }
+
+    // 关闭弹窗
+    const handleCancel = () => {
+        setIsModalOpen(false)
+        setCurrentProduct(null)
+        form.resetFields()
+        // 重置提交状态
+        setSubmitting(false)
+    }
+
+    // 提交报价表单(完善接口调用逻辑)
+    const handleSubmit = async (values: QuoteFormValues) => {
+        try {
+            // 1. 设置提交中状态,禁用按钮
+            setSubmitting(true)
+            const response = await serverPost<Page<QuoteFormValues>, {}>(
+                "/webSite/save",
+                {
+                    content: values.message,
+                    deviceName: values.productName,
+                    name: values.name,
+                    phone: values.phone
+                }, {
+                    next: {
+                        revalidate: 30
+                    },
+                    cache: "force-cache"
+                }
+            )
+            // 5. 提交成功提示
+            message.success("报价请求提交成功!我们会尽快联系你")
+            // 6. 关闭弹窗并重置表单
+            handleCancel()
+        } catch (error) {
+            // 7. 异常处理
+            const errorMsg = error instanceof Error ? error.message : "提交失败,请稍后重试"
+            message.error(errorMsg)
+            console.error("提交报价失败:", error)
+        } finally {
+            // 8. 无论成功失败,都重置提交状态
+            setSubmitting(false)
+        }
+    }
+
     return (
         <div className="flex flex-col gap-6">
             {/* 产品网格 */}
@@ -34,23 +119,25 @@ export default function ProductGrid({products}: ProductGridProps) {
                                 alt={product.productName}
                                 width={1000}
                                 height={1000}
+                                priority={false}
                             />
                         </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>
 
                             <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"
+                                    onClick={() => showModal(product)}
+                                    className="bg-red-600 text-white !text-white px-4 !px-4 py-1.5 !py-1.5 rounded 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
+                                    href={`/products/${product.productId}`}
+                                    className="bg-blue-600 text-white !text-white px-4 !px-4 py-1.5 !py-1.5 rounded hover:bg-blue-700 transition-colors duration-200 ml-2"
+                                >
                                     了解详情
                                 </Link>
                             </div>
@@ -69,6 +156,80 @@ export default function ProductGrid({products}: ProductGridProps) {
                     showSizeChanger={false}
                 />
             </div>
+
+            {/* 报价弹窗(移除采购数量相关内容) */}
+            <Modal
+                title={`获取 "${currentProduct?.productName}" 报价`}
+                open={isModalOpen}
+                onCancel={handleCancel}
+                footer={null}
+                destroyOnClose={true}
+                maskClosable={false}
+                width={600}
+            >
+                <Form
+                    form={form}
+                    layout="vertical"
+                    onFinish={handleSubmit}
+                >
+                    {/* 隐藏字段 */}
+                    <Form.Item name="productId" hidden>
+                        <Input/>
+                    </Form.Item>
+                    <Form.Item name="productName" hidden>
+                        <Input/>
+                    </Form.Item>
+
+                    {/* 联系人姓名 */}
+                    <Form.Item
+                        name="name"
+                        label="联系人姓名"
+                        rules={[{required: true, message: "请输入您的姓名"}]}
+                    >
+                        <Input placeholder="请输入您的姓名"/>
+                    </Form.Item>
+
+                    {/* 联系电话 */}
+                    <Form.Item
+                        name="phone"
+                        label="联系电话"
+                        rules={[
+                            {required: true, message: "请输入您的联系电话"},
+                            {pattern: /^1[3-9]\d{9}$/, message: "请输入有效的手机号码"}
+                        ]}
+                    >
+                        <Input placeholder="请输入您的手机号码"/>
+                    </Form.Item>
+
+                    {/* 留言/备注 */}
+                    <Form.Item
+                        name="message"
+                        label="留言/备注"
+                    >
+                        <Input.TextArea
+                            rows={4}
+                            placeholder="请输入您的其他需求或备注信息(选填)"
+                        />
+                    </Form.Item>
+
+                    {/* 表单操作按钮(添加加载状态) */}
+                    <Form.Item>
+                        <div className="flex justify-end gap-2">
+                            <Button onClick={handleCancel} type="default" disabled={submitting}>
+                                取消
+                            </Button>
+                            <Button
+                                type="primary"
+                                htmlType="submit"
+                                loading={submitting} // 提交中显示加载动画
+                                disabled={submitting} // 禁用按钮防止重复提交
+                            >
+                                提交报价请求
+                            </Button>
+                        </div>
+                    </Form.Item>
+                </Form>
+            </Modal>
         </div>
     )
-}
+}