Ver Fonte

insightList & buyArticle

wind há 3 semanas atrás
pai
commit
938190a71c

+ 25 - 0
next.config.mjs

@@ -10,6 +10,31 @@ const nextConfig = {
       },
     ],
   },
+
+  async rewrites() {
+    return [
+      {
+        source: "/api/v1/cms/listArticle",
+        // destination: "http://localhost:8089/api/v1/cms/listArticle",
+        destination: "https://hi-po.com.cn/api/v1/cms/listArticle",
+      },
+      {
+        source: "/api/v1/cms/articleDetail",
+        // destination: "http://localhost:8089/api/v1/cms/articleDetail",
+        destination: "https://hi-po.com.cn/api/v1/cms/articleDetail",
+      },
+      {
+        source: "/api/v1/order/buyArticle",
+        // destination: "http://localhost:8089/api/v1/order/buyArticle",
+        destination: "https://hi-po.com.cn/api/v1/order/buyArticle",
+      },
+      {
+        source: "/api/v1/user/:path*",
+        // destination: "http://localhost:8089/api/v1/user/:path*",
+        destination: "https://hi-po.com.cn/api/v1/user/:path*",
+      },
+    ];
+  },
 };
 
 export default nextConfig;

+ 98 - 0
src/app/insightDetail/[slug]/insightDetail.module.scss

@@ -0,0 +1,98 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  background: white;
+
+  .content {
+    margin-top: 30px;
+    width: 1160px;
+    text-align: left;
+    font-weight: 600;
+
+    .breadcrumb-item {
+      box-sizing: border-box;
+      margin: 0;
+      padding: 0;
+      color: rgba(0, 0, 0, 0.45);
+      font-variant: tabular-nums;
+      line-height: 1.5715;
+      list-style: none;
+      font-size: 14px;
+
+      .breadcrumb-last {
+        color: #1773c8;
+      }
+    }
+
+    .breadcrumb-seperator {
+      box-sizing: border-box;
+      margin-left: 10px;
+      margin-right: 10px;
+      padding: 0;
+      color: rgba(0, 0, 0, 0.45);
+      font-variant: tabular-nums;
+      line-height: 1.5715;
+      list-style: none;
+      font-size: 14px;
+    }
+
+    .title {
+      font-size: 20px;
+      color: #333;
+      margin-top: 30px;
+    }
+
+    .author {
+      font-size: 14px;
+      color: #999;
+      margin-top: 12px;
+    }
+
+    .line {
+      height: 1px;
+      width: 100%;
+      background-color: #eee;
+      margin-top: 12px;
+      margin-bottom: 24px;
+    }
+
+    .digest {
+      background: #f0f0f0;
+      padding: 16px;
+      border-radius: 8px;
+      margin-top: 12px;
+      color: #888;
+      line-height: 24px;
+    }
+
+    .articleContent {
+      color: #666;
+    }
+
+    .articleContent img {
+      display: block;
+      max-width: 60%;
+      margin: 0 auto;
+    }
+  }
+}
+
+@media screen and (max-width: 767px) {
+  .container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    background: white;
+
+    .content {
+      margin-top: 100px;
+      width: 90%;
+
+      .articleContent img {
+        width: 100%;
+        max-width: 100%;
+      }
+    }
+  }
+}

+ 69 - 0
src/app/insightDetail/[slug]/page.tsx

@@ -0,0 +1,69 @@
+"use client";
+import React, { useState, useEffect } from "react";
+import Nav from "@/components/Nav";
+import Footer from "@/components/Footer";
+import styles from "./insightDetail.module.scss";
+import { fetchArticleDetail } from "@/lib/cmsForPayment";
+import { getDesc180 } from "@/lib/parser";
+
+interface ArticleType {
+  id: number;
+  title?: string | null;
+  digest?: string | null;
+  author?: string | null;
+  type: string;
+  content?: string | null;
+  cover: string;
+  payed: boolean;
+  order?: number | null;
+  status?: string | null;
+  createTime?: Date | null;
+  updateTime?: Date | null;
+}
+
+export default function ArticleDetail({ params }: { params: { slug: string } }) {
+  const articleId = params.slug.replace(".html", "");
+  const [article, setArticle] = useState<ArticleType | null>(null);
+  const [message, setMessage] = useState<string | null>(null);
+  useEffect(() => {
+    fetchArticleDetail(parseInt(articleId as string)).then((data) => {
+      console.log(data);
+      if (data.code == 9001) {
+        setMessage("此文章为付费文章,请返回列表页面进行购买!");
+      } else {
+        setArticle(data.data);
+      }
+    });
+  }, []);
+
+  return (
+    <>
+      <div className={styles.container}>
+        <Nav title="行业洞察" />
+        <div className={styles.content}>
+          <div>
+            <span className={styles["breadcrumb-item"]}>
+              <a href="/home">首页</a>
+            </span>
+            <span className={styles["breadcrumb-seperator"]}>/</span>
+            <span className={styles["breadcrumb-item"]}>
+              <a href="/insightList">行业洞察</a>
+            </span>
+            <span className={styles["breadcrumb-seperator"]}>/</span>
+            <span className={styles["breadcrumb-item"]}>
+              <span className={styles["breadcrumb-last"]}>{article?.title}</span>
+            </span>
+          </div>
+          <div className={styles.title}>{article?.title}</div>
+          <div className={styles.author}>{article?.author}</div>
+          <div className={styles.line}></div>
+          {article?.content && (
+            <div className={styles.articleContent} dangerouslySetInnerHTML={{ __html: article?.content }}></div>
+          )}
+          <div className={styles.articleContent}>{message}</div>
+        </div>
+        <Footer />
+      </div>
+    </>
+  );
+}

+ 134 - 0
src/app/insightList/insightList.module.scss

@@ -0,0 +1,134 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  background: white;
+
+  .content {
+    margin-top: 20px;
+
+    .articleTitle {
+      font-size: 18px;
+      color: #333333;
+      font-weight: 600;
+    }
+
+    .payment {
+      margin-left: 20px;
+      padding: 6px;
+      font-size: 12px;
+      color: #f60707;
+      background-color: #fefefe;
+      box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
+      border-radius: 4px;
+    }
+
+    .articleContent {
+      font-size: 16px;
+      line-height: 30px;
+      margin-top: 12px;
+      color: #555555;
+      text-align: justify;
+    }
+
+    .articleList {
+      width: 1160px;
+      margin-top: 40px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      gap: 20px;
+
+      .articleItem {
+        display: flex;
+        flex-direction: row;
+        border-radius: 4px;
+        overflow: hidden;
+
+        .articleText {
+          width: 777px;
+          padding: 24px;
+          background: white;
+          height: 220px;
+          text-overflow: ellipsis;
+          overflow: hidden;
+        }
+
+        .articleImg {
+          width: 315px;
+          height: 212px;
+          margin-right: 20px;
+          object-fit: cover;
+        }
+      }
+    }
+  }
+}
+
+@media screen and (max-width: 767px) {
+  .container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    background: white;
+
+    .content {
+      margin-top: 50px;
+
+      .articleTitle {
+        font-size: 18px;
+        color: #333333;
+        font-weight: 600;
+      }
+
+      .payment {
+        margin-left: 20px;
+        padding: 5px;
+        font-size: 12px;
+        color: #f60707;
+        background-color: #e7dbdb;
+        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+        border-radius: 8px;
+      }
+
+      .articleContent {
+        font-size: 16px;
+        line-height: 30px;
+        margin-top: 12px;
+        color: #555555;
+        text-align: justify;
+      }
+
+      .articleList {
+        width: 90%;
+        margin-top: 40px;
+        display: flex;
+        margin-left: 5%;
+        flex-direction: column;
+        align-items: center;
+        flex-grow: 1;
+        background: #ffffff;
+
+        .articleItem {
+          display: flex;
+          flex-direction: column;
+          border: #eeeeee 1px solid;
+          border-radius: 4px;
+
+          .articleText {
+            padding: 20px;
+            background: white;
+            height: 310px;
+          }
+
+          .articleImg {
+            width: 100%;
+            height: 220px;
+            object-fit: cover;
+            overflow: hidden;
+          }
+        }
+      }
+    }
+  }
+}

+ 80 - 0
src/app/insightList/page.tsx

@@ -0,0 +1,80 @@
+"use client";
+
+import React, { useState, useEffect } from "react";
+import Image from "next/image";
+import Nav from "@/components/Nav";
+import Footer from "@/components/Footer";
+import styles from "./insightList.module.scss";
+import { fetchArticleList, buyArticle } from "@/lib/cmsForPayment";
+
+interface ArticleType {
+  id: number;
+  title?: string | null;
+  digest?: string | null;
+  author?: string | null;
+  type?: string | null;
+  content?: string | null;
+  cover: string;
+  payed: boolean;
+  order?: number | null;
+  status?: string | null;
+  createTime?: Date | null;
+  updateTime?: Date | null;
+}
+
+const InsightList: React.FC = () => {
+  const [insightList, setInsightList] = useState<ArticleType[] | null>(null);
+  useEffect(() => {
+    fetchArticleList(999, 1, 100).then(({ data: list }) => {
+      const records = [...list?.records];
+      setInsightList(records);
+    });
+  }, []);
+
+  const buy = (event: React.MouseEvent, id: number) => {
+    event.preventDefault();
+    buyArticle(id).then((data) => {
+      if (data.code == 200) {
+        const newWindow = window.open("", "_self");
+        newWindow?.document.write(data.data);
+        newWindow?.focus();
+      }
+    });
+    return false;
+  };
+
+  return (
+    <>
+      <div className={styles.container}>
+        <Nav title={"行业洞察"} />
+        <div className={styles.content}>
+          <div className={styles.articleList}>
+            {insightList?.map((item: ArticleType, index: number) => {
+              return (
+                <a href={`/insightDetail/${item.id}.html`} key={index}>
+                  <div className={styles.articleItem}>
+                    <Image alt="pic" width={315} height={212} className={styles.articleImg} src={item?.cover} />
+
+                    <div className={styles.articleText}>
+                      <div className={styles.articleTitle}>
+                        {item?.title}
+                        {!item?.payed && (
+                          <span className={styles.payment} onClick={(event) => buy(event, item?.id)}>
+                            需购买
+                          </span>
+                        )}
+                      </div>
+                      <div className={styles.articleContent}>{item?.digest}</div>
+                    </div>
+                  </div>
+                </a>
+              );
+            })}
+          </div>
+        </div>
+        <Footer />
+      </div>
+    </>
+  );
+};
+export default InsightList;

+ 3 - 3
src/components/Header/header.module.scss

@@ -67,7 +67,7 @@
       .navbar-login {
         position: absolute;
         top: 0px;
-        left: 255px;
+        left: 355px;
         width: 65px;
         height: 24px;
       }
@@ -75,7 +75,7 @@
       .navbar-register {
         position: absolute;
         top: 0px;
-        left: 313px;
+        left: 413px;
         width: 65px;
         height: 24px;
       }
@@ -83,7 +83,7 @@
       .navbar-user {
         position: absolute;
         top: 0px;
-        left: 255px;
+        left: 355px;
         width: 100px;
         height: 24px;
         text-align: center;

+ 3 - 0
src/components/Header/headerMobile.tsx

@@ -63,6 +63,9 @@ const HeaderMobile: React.FC = () => {
             <Link className={styles.item} href="/caseList">
               经典案例
             </Link>
+            <Link className={styles.item} href="/insightList">
+              行业洞察
+            </Link>
             <Link className={styles.item} href="/contactUs">
               联系我们
             </Link>

+ 4 - 1
src/components/Header/headerPC.tsx

@@ -64,13 +64,16 @@ const HeaderPC: React.FC = () => {
             <Link className={styles.link} href="/caseList">
               经典案例
             </Link>
+            <Link className={styles.link} href="/insightList">
+              行业洞察
+            </Link>
             <Link className={styles.link} href="/contactUs">
               联系我们
             </Link>
           </div>
           {isLogin && (
             <>
-              <div className={styles["navbar-user"]}>
+              <div className={styles["navbar-user"]} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
                 <Link className={styles.link} href="#" onClick={() => toggleDropdown()}>
                   {user?.phone}
                 </Link>

+ 51 - 1
src/components/Nav/nav.module.scss

@@ -51,7 +51,7 @@
 
     .navbar-collapse {
       position: absolute;
-      width: 600px;
+      width: 700px;
       height: 80px;
       right: 0;
 
@@ -97,6 +97,56 @@
 
         color: #1773c8;
       }
+
+      .navbar-user {
+        // position: absolute;
+        // top: 0px;
+        // left: 255px;
+        position: relative;
+        width: 100px;
+        height: 24px;
+        text-align: center;
+
+        .link {
+          color: #333333;
+          font-weight: 600;
+          font-size: 14px;
+          font-family: "Montserrat";
+          font-style: normal;
+          line-height: 24px;
+          letter-spacing: 0.2px;
+          transition: box-shadow 0.3s ease, color 0.3s ease;
+        }
+
+        .hide-drop {
+          position: absolute;
+          display: none;
+          top: 100%;
+          left: 0;
+          text-align: center;
+          padding-top: 20px;
+          padding-bottom: 5px;
+          background-color: rgba(197, 197, 197, 0.2);
+          min-width: 100px;
+          z-index: 1000;
+        }
+
+        .show-drop {
+          position: absolute;
+          display: block;
+          top: 100%;
+          left: 0;
+          text-align: center;
+          padding-top: 20px;
+          padding-bottom: 5px;
+          background-color: rgba(197, 197, 197, 0.2);
+          min-width: 100px;
+          z-index: 1000;
+        }
+      }
+      .navbar-user:hover {
+        background-color: rgba(197, 197, 197, 0.2); /* 可选:微弱的背景颜色 */
+      }
     }
   }
 }

+ 49 - 7
src/components/Nav/navMobile.tsx

@@ -1,9 +1,11 @@
 "use client";
 
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
 import Image from "next/image";
 import Link from "next/link";
 import styles from "./nav.module.scss";
+import { useCommonContext } from "@/context/commonContext";
+import Cookies from "js-cookie";
 
 const NavMobile: React.FC = () => {
   const [showMenu, setShowMenu] = useState("");
@@ -12,6 +14,22 @@ const NavMobile: React.FC = () => {
     setShowMenu("hide");
   };
 
+  const [isLogin, setIsLogin] = useState(false);
+  const { user, setUser } = useCommonContext();
+
+  useEffect(() => {
+    if (user) {
+      setIsLogin(true);
+    } else {
+      setIsLogin(false);
+    }
+  }, [user]);
+
+  const logout = () => {
+    setUser(null);
+    Cookies.remove("ut");
+  };
+
   return (
     <>
       <div className={styles["nav-fixed"]}>
@@ -46,15 +64,39 @@ const NavMobile: React.FC = () => {
             <Link className={styles.item} href="/caseList">
               经典案例
             </Link>
+            <Link className={styles.link} href="/insightList">
+              行业洞察
+            </Link>
             <Link className={styles.item} href="/contactUs">
               联系我们
             </Link>
-            <Link className={styles.item} href={"/user/login"}>
-              登录
-            </Link>
-            <Link className={styles.item} href={"/user/register"}>
-              注册
-            </Link>
+            {isLogin && (
+              <>
+                <Link className={styles.item} href="#" onClick={() => hideMenu()}>
+                  {user?.phone}
+                </Link>
+                <Link
+                  className={styles.item}
+                  href="#"
+                  onClick={() => {
+                    logout();
+                    hideMenu();
+                  }}
+                >
+                  退出
+                </Link>
+              </>
+            )}
+            {!isLogin && (
+              <>
+                <Link className={styles.item} href={"/user/login"}>
+                  登录
+                </Link>
+                <Link className={styles.item} href={"/user/register"}>
+                  注册
+                </Link>
+              </>
+            )}
           </div>
         </div>
       </div>

+ 71 - 7
src/components/Nav/navPC.tsx

@@ -1,14 +1,49 @@
 "use client";
 
-import React from "react";
+import React, { useState, useEffect } from "react";
 import Link from "next/link";
 import styles from "./nav.module.scss";
+import { useCommonContext } from "@/context/commonContext";
+import Cookies from "js-cookie";
 
 interface NavPCProps {
   title: string;
 }
 
 const NavPC: React.FC<NavPCProps> = ({ title }) => {
+  const [showDropdown, setDropdown] = useState(false);
+
+  const toggleDropdown = () => {
+    if (showDropdown) {
+      setDropdown(false);
+    } else {
+      setDropdown(true);
+    }
+  };
+  const handleMouseEnter = () => {
+    setDropdown(true);
+  };
+
+  const handleMouseLeave = () => {
+    setDropdown(false);
+  };
+
+  const [isLogin, setIsLogin] = useState(false);
+  const { user, setUser } = useCommonContext();
+
+  useEffect(() => {
+    if (user) {
+      setIsLogin(true);
+    } else {
+      setIsLogin(false);
+    }
+  }, [user]);
+
+  const logout = () => {
+    setUser(null);
+    Cookies.remove("ut");
+  };
+
   const isCurrent = (currentTitle: string) => {
     return currentTitle === title;
   };
@@ -37,15 +72,44 @@ const NavPC: React.FC<NavPCProps> = ({ title }) => {
             <Link className={isCurrent("经典案例") ? styles.linkChecked : styles.link} href="/caseList">
               经典案例
             </Link>
+            <Link className={isCurrent("行业洞察") ? styles.linkChecked : styles.link} href="/insightList">
+              行业洞察
+            </Link>
             <Link className={isCurrent("联系我们") ? styles.linkChecked : styles.link} href="/contactUs">
               联系我们
             </Link>
-            <Link className={styles.link} href={"/user/login"}>
-              登录
-            </Link>
-            <Link className={styles.link} href={"/user/register"}>
-              注册
-            </Link>
+            {isLogin && (
+              <>
+                <div className={styles["navbar-user"]} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
+                  <Link className={styles.link} href="#" onClick={() => toggleDropdown()}>
+                    {user?.phone}
+                  </Link>
+                  <div
+                    className={showDropdown ? styles["show-drop"] : styles["hide-drop"]}
+                    onMouseEnter={handleMouseEnter}
+                    onMouseLeave={handleMouseLeave}
+                  >
+                    <Link className={styles.link} href="#" onClick={() => logout()}>
+                      退出
+                    </Link>
+                  </div>
+                </div>
+              </>
+            )}
+            {!isLogin && (
+              <>
+                <div className={styles["navbar-login"]}>
+                  <Link className={styles.link} href={"/user/login"}>
+                    登录
+                  </Link>
+                </div>
+                <div className={styles["navbar-register"]}>
+                  <Link className={styles.link} href={"/user/register"}>
+                    注册
+                  </Link>
+                </div>
+              </>
+            )}
           </div>
         </div>
       </div>

+ 9 - 2
src/context/commonContext.tsx

@@ -25,8 +25,15 @@ export function CommonProvider({ children }: { children: React.ReactNode }) {
 
   useEffect(() => {
     const ut = Cookies.get("ut");
-    userInfo(ut).then(({ code: data }) => {
-      console.log(data);
+    userInfo(ut).then(({ data: data }) => {
+      if (data) {
+        const currentUser = {
+          id: data.id,
+          name: data.name,
+          phone: data.phone,
+        };
+        setUser(currentUser);
+      }
     });
   }, []);
 

+ 40 - 0
src/lib/cmsForPayment.ts

@@ -0,0 +1,40 @@
+const host = "";
+
+export async function fetchArticleList(type: number, page: number, size: number) {
+  const formData = new FormData();
+  formData.append("type", type.toString());
+  formData.append("page", page.toString());
+  formData.append("size", size.toString());
+
+  const result = await fetch(`${host}/api/v1/cms/listArticle`, {
+    method: "POST",
+    body: formData,
+    cache: "no-store",
+  });
+  const json = await result.json();
+  return json;
+}
+
+export async function fetchArticleDetail(id: number) {
+  const formData = new FormData();
+  formData.append("id", id.toString());
+
+  const result = await fetch(`${host}/api/v1/cms/articleDetail`, {
+    method: "POST",
+    body: formData,
+  });
+  const json = await result.json();
+  return json;
+}
+
+export async function buyArticle(id: number) {
+  const formData = new FormData();
+  formData.append("articleId", id.toString());
+
+  const result = await fetch(`${host}/api/v1/order/buyArticle`, {
+    method: "POST",
+    body: formData,
+  });
+  const json = await result.json();
+  return json;
+}

+ 2 - 1
src/lib/user.ts

@@ -1,4 +1,5 @@
-const host = "https://hi-po.com.cn";
+// const host = "https://hi-po.com.cn";
+const host = "";
 
 export async function sendCaptcha(phone: string) {
   const formData = new FormData();