3
0

2 Commity 8a31301187 ... 938190a71c

Autor SHA1 Správa Dátum
  wind 938190a71c insightList & buyArticle 3 týždňov pred
  wind 72a715f8d7 login & register 3 týždňov pred

+ 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;

+ 1 - 0
package.json

@@ -11,6 +11,7 @@
   "dependencies": {
     "@ant-design/pro-components": "^2.8.2",
     "antd": "^5.22.4",
+    "js-cookie": "^3.0.5",
     "jsdom": "^25.0.1",
     "next": "14.2.20",
     "react": "^18.0.0",

+ 1 - 1
src/app/(home)/components/Services/services.module.scss

@@ -49,7 +49,7 @@
           flex: none;
           flex-grow: 0;
           order: 0;
-          width: 201px;
+          width: 240px;
           height: 57px;
 
           /* text-color */

+ 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;

+ 4 - 1
src/app/layout.tsx

@@ -1,6 +1,7 @@
 import type { Metadata } from "next";
 import type { Viewport } from "next";
 import "./globals.css";
+import { CommonProvider } from "@/context/commonContext";
 
 export const viewport: Viewport = {
   width: "device-width",
@@ -40,7 +41,9 @@ export default function RootLayout({
         <meta httpEquiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
         <meta httpEquiv="X-UA-Compatible" content="IE=8" />
       </head>
-      <body>{children}</body>
+      <body>
+        <CommonProvider>{children}</CommonProvider>
+      </body>
     </html>
   );
 }

+ 11 - 17
src/app/user/login/login.module.css

@@ -3,33 +3,22 @@
   flex-direction: column;
   height: 100vh;
   overflow: auto;
-  background: @layout-body-background;
-}
-
-.lang {
-  width: 100%;
-  height: 40px;
-  line-height: 44px;
-  text-align: right;
-  :global(.ant-dropdown-trigger) {
-    margin-right: 24px;
-  }
 }
 
 .content {
   flex: 1;
+  margin: 100px 0;
   padding: 32px 0;
 }
 
-@media (min-width: @screen-md-min) {
+@media screen and (max-width: 767px) {
   .container {
-    background-image: url("https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg");
-    background-repeat: no-repeat;
     background-position: center 110px;
     background-size: 100%;
   }
 
   .content {
+    margin: 100px 0;
     padding: 32px 0 24px;
   }
 }
@@ -41,8 +30,13 @@
   vertical-align: middle;
   cursor: pointer;
   transition: color 0.3s;
+}
 
-  &:hover {
-    color: @primary-color;
-  }
+.footer {
+  position: absolute;
+  bottom: 0;
+  width: 100%;
+  background-color: #f8f9fa;
+  text-align: center;
+  padding: 1rem 0;
 }

+ 187 - 187
src/app/user/login/page.tsx

@@ -1,198 +1,198 @@
-// import { LockOutlined, MobileOutlined } from "@ant-design/icons";
-// import { LoginForm, ProForm, ProFormCaptcha, ProFormText } from "@ant-design/pro-components";
-// import { Alert, message, Tabs } from "antd";
-// import React, { useState } from "react";
-// import { history, SelectLang } from "umi";
-// import styles from "./login.module.css";
-// import { login, sendCaptcha } from "@/lib/user";
-// import Cookies from "js-cookie";
+"use client";
+
+import { GoDeviceMobile, GoLock } from "react-icons/go";
+import { LoginForm, ProForm, ProFormCaptcha, ProFormText } from "@ant-design/pro-components";
+import { Alert, message, Tabs } from "antd";
+import React, { useState } from "react";
+import styles from "./login.module.scss";
+import { login, sendCaptcha } from "@/lib/user";
+
+import { useRouter } from "next/navigation";
+import Cookies from "js-cookie";
+import { useCommonContext } from "@/context/commonContext";
 // import { useModel } from "@@/plugin-model/useModel";
 // import { USER_TYPE_ADMIN, USER_TYPE_PLATFORM } from "@/services/constants";
 
-// const LoginMessage: React.FC<{ content: string }> = ({ content }) => (
-//   <Alert
-//     style={{
-//       marginBottom: 24,
-//     }}
-//     message={content}
-//     type="error"
-//     showIcon
-//   />
-// );
+type LoginParams = {
+  type?: number;
+  username?: string;
+  password?: string;
+  captcha?: string;
+  sourceId?: number;
+};
 
-const Login: React.FC = () => {
-  //   const [errorMsg, setErrorMsg] = useState<string>("");
-  //   const [type, setType] = useState<string>("password");
-  //   const [form] = ProForm.useForm();
-  //   const { initialState, setInitialState } = useModel("@@initialState");
+const LoginMessage: React.FC<{ content: string }> = ({ content }) => (
+  <Alert
+    style={{
+      marginBottom: 24,
+    }}
+    message={content}
+    type="error"
+    showIcon
+  />
+);
 
-  //   const fetchUserInfo = async () => {
-  //     const userInfo = await initialState?.fetchUserInfo?.();
-  //     if (userInfo) {
-  //       await setInitialState((s) => ({
-  //         ...s,
-  //         currentUser: userInfo,
-  //       }));
-  //     }
-  //   };
+export default function Login() {
+  const router = useRouter();
+  const [errorMsg, setErrorMsg] = useState<string>("");
+  const [type, setType] = useState<string>("password");
+  const [form] = ProForm.useForm();
+  const { redirectAfterLogin, setUser } = useCommonContext();
 
-  //   const handleSubmit = async (values: API.LoginParams) => {
-  //     try {
-  //       const response = await login({ ...values, type: type == "password" ? 0 : 1 });
-  //       console.log(response);
-  //       if (response.code === 200) {
-  //         if (response.data?.type !== USER_TYPE_ADMIN && response.data?.type !== USER_TYPE_PLATFORM) {
-  //           message.success("登录成功");
-  //           await setInitialState((s) => ({
-  //             ...s,
-  //             currentUser: response.data,
-  //           }));
-  //           Cookies.set("ut", response!.data!.token!);
-  //           await fetchUserInfo();
-  //           if (!history) return;
-  //           const { query } = history.location;
-  //           const { redirect } = query as { redirect: string };
-  //           history.push(redirect || "/welcome");
-  //           return;
-  //         } else {
-  //           setErrorMsg("登录平台错误");
-  //           return;
-  //         }
-  //       }
-  //       // 如果失败去设置用户错误信息
-  //       setErrorMsg(response.message || "");
-  //     } catch (error) {
-  //       message.error("登录失败,请重试!");
-  //     }
-  //   };
+  const handleSubmit = async (values: LoginParams) => {
+    try {
+      const response = await login({ ...values, type: type == "password" ? 0 : 1 });
+      console.log(response);
+      if (response.code === 200) {
+        const data = response.data;
+        Cookies.set("ut", data.token);
 
-  return <div>hi</div>;
-  //     <div className={styles.container}>
-  //       <div className={styles.content}>
-  //         <LoginForm
-  //           form={form}
-  //           title="高潜咨询"
-  //           subTitle={"中国企业人效的未来"}
-  //           initialValues={{}}
-  //           actions={[]}
-  //           onFinish={async (values) => {
-  //             await handleSubmit(values as API.LoginParams);
-  //           }}
-  //         >
-  //           <Tabs activeKey={type} onChange={setType}>
-  //             <Tabs.TabPane key="password" tab={"手机号密码登录"} />
-  //             <Tabs.TabPane key="captcha" tab={"手机号验证码登录"} />
-  //           </Tabs>
+        const user = {
+          id: data.id,
+          name: data.name,
+          phone: data.phone,
+        };
+        setUser(user);
+        router.push(redirectAfterLogin);
+        return;
+      }
+      setErrorMsg(response.message || "");
+    } catch (error) {
+      message.error("登录失败,请重试!");
+    }
+  };
 
-  //           {errorMsg && <LoginMessage content={errorMsg} />}
+  const tabItems = [
+    {
+      key: "password",
+      label: "手机号密码登录",
+    },
+    {
+      key: "captcha",
+      label: "手机号验证码登录",
+    },
+  ];
 
-  //           {type === "password" && (
-  //             <>
-  //               <ProFormText
-  //                 fieldProps={{
-  //                   size: "large",
-  //                   prefix: <MobileOutlined className={styles.prefixIcon} />,
-  //                 }}
-  //                 name="phone"
-  //                 placeholder="手机号"
-  //                 rules={[
-  //                   {
-  //                     required: true,
-  //                     message: "请输入手机号!",
-  //                   },
-  //                   {
-  //                     pattern: /^1\d{10}$/,
-  //                     message: "手机号格式错误!",
-  //                   },
-  //                 ]}
-  //               />
-  //               <ProFormText.Password
-  //                 name="password"
-  //                 fieldProps={{
-  //                   size: "large",
-  //                   prefix: <LockOutlined className={styles.prefixIcon} />,
-  //                 }}
-  //                 placeholder={"密码"}
-  //                 rules={[
-  //                   {
-  //                     required: true,
-  //                     message: "请输入密码",
-  //                   },
-  //                 ]}
-  //               />
-  //             </>
-  //           )}
+  return (
+    <div className={styles.container}>
+      <div className={styles.content}>
+        <LoginForm
+          form={form}
+          title="高潜咨询"
+          subTitle={"中国企业人效的未来"}
+          initialValues={{}}
+          actions={[]}
+          onFinish={async (values) => {
+            await handleSubmit(values as LoginParams);
+          }}
+        >
+          <Tabs activeKey={type} onChange={setType} items={tabItems} />
 
-  //           {type === "captcha" && (
-  //             <>
-  //               <ProFormText
-  //                 fieldProps={{
-  //                   size: "large",
-  //                   prefix: <MobileOutlined className={styles.prefixIcon} />,
-  //                 }}
-  //                 name="phone"
-  //                 placeholder="手机号"
-  //                 rules={[
-  //                   {
-  //                     required: true,
-  //                     message: "请输入手机号!",
-  //                   },
-  //                   {
-  //                     pattern: /^1\d{10}$/,
-  //                     message: "手机号格式错误!",
-  //                   },
-  //                 ]}
-  //               />
-  //               <ProFormCaptcha
-  //                 fieldProps={{
-  //                   size: "large",
-  //                   prefix: <LockOutlined className={styles.prefixIcon} />,
-  //                 }}
-  //                 captchaProps={{
-  //                   size: "large",
-  //                 }}
-  //                 placeholder="请输入验证码"
-  //                 captchaTextRender={(timing, count) => {
-  //                   if (timing) {
-  //                     return `${count} 获取验证码`;
-  //                   }
-  //                   return "获取验证码";
-  //                 }}
-  //                 name="captcha"
-  //                 rules={[
-  //                   {
-  //                     required: true,
-  //                     message: "请输入验证码!",
-  //                   },
-  //                 ]}
-  //                 onGetCaptcha={async () => {
-  //                   const phone = form.getFieldValue("phone");
-  //                   if (!phone) {
-  //                     message.error("请先输入手机号");
-  //                     throw new Error("请先输入手机号");
-  //                   }
-  //                   const result = await sendCaptcha({
-  //                     phone,
-  //                   });
-  //                   console.log(result);
-  //                   if (result === false) {
-  //                     return;
-  //                   }
-  //                   message.success("获取验证码成功");
-  //                 }}
-  //               />
-  //             </>
-  //           )}
-  //           <div
-  //             style={{
-  //               marginBottom: 24,
-  //             }}
-  //           ></div>
-  //         </LoginForm>
-  //       </div>
-  //       <div>@2024 高潜咨询</div>
-  //     </div>
-  //   );
-};
+          {errorMsg && <LoginMessage content={errorMsg} />}
+
+          {type === "password" && (
+            <>
+              <ProFormText
+                fieldProps={{
+                  size: "large",
+                  prefix: <GoDeviceMobile className={styles.prefixIcon} />,
+                }}
+                name="phone"
+                placeholder="手机号"
+                rules={[
+                  {
+                    required: true,
+                    message: "请输入手机号!",
+                  },
+                  {
+                    pattern: /^1\d{10}$/,
+                    message: "手机号格式错误!",
+                  },
+                ]}
+              />
+              <ProFormText.Password
+                name="password"
+                fieldProps={{
+                  size: "large",
+                  prefix: <GoLock className={styles.prefixIcon} />,
+                }}
+                placeholder={"密码"}
+                rules={[
+                  {
+                    required: true,
+                    message: "请输入密码",
+                  },
+                ]}
+              />
+            </>
+          )}
 
-export default Login;
+          {type === "captcha" && (
+            <>
+              <ProFormText
+                fieldProps={{
+                  size: "large",
+                  prefix: <GoDeviceMobile className={styles.prefixIcon} />,
+                }}
+                name="phone"
+                placeholder="手机号"
+                rules={[
+                  {
+                    required: true,
+                    message: "请输入手机号!",
+                  },
+                  {
+                    pattern: /^1\d{10}$/,
+                    message: "手机号格式错误!",
+                  },
+                ]}
+              />
+              <ProFormCaptcha
+                fieldProps={{
+                  size: "large",
+                  prefix: <GoLock className={styles.prefixIcon} />,
+                }}
+                captchaProps={{
+                  size: "large",
+                }}
+                placeholder="请输入验证码"
+                captchaTextRender={(timing, count) => {
+                  if (timing) {
+                    return `${count} 获取验证码`;
+                  }
+                  return "获取验证码";
+                }}
+                name="captcha"
+                rules={[
+                  {
+                    required: true,
+                    message: "请输入验证码!",
+                  },
+                ]}
+                onGetCaptcha={async () => {
+                  const phone = form.getFieldValue("phone");
+                  if (!phone) {
+                    message.error("请先输入手机号");
+                    throw new Error("请先输入手机号");
+                  }
+                  const result = await sendCaptcha(phone);
+                  console.log(result);
+                  if (result === false) {
+                    return;
+                  }
+                  message.success("获取验证码成功");
+                }}
+              />
+            </>
+          )}
+          <div
+            style={{
+              marginBottom: 24,
+            }}
+          ></div>
+        </LoginForm>
+      </div>
+      <div className={styles.footer}>@2024 高潜咨询</div>
+    </div>
+  );
+}

+ 141 - 0
src/app/user/register/page.tsx

@@ -0,0 +1,141 @@
+"use client";
+
+import { GoDeviceMobile, GoLock } from "react-icons/go";
+import { ProForm, ProFormCaptcha, ProFormText } from "@ant-design/pro-components";
+import { message } from "antd";
+import React from "react";
+import { register, sendCaptcha } from "@/lib/user";
+import styles from "./register.module.scss";
+import { useRouter } from "next/navigation";
+
+type RegisterParams = {
+  phone?: string;
+  password?: string;
+  inviteCode?: string;
+  captcha?: string;
+};
+
+const Register: React.FC = () => {
+  const router = useRouter();
+  const [form] = ProForm.useForm();
+
+  const handleSubmit = async (values: RegisterParams) => {
+    register({ ...values }).then((data) => {
+      if (data.code === 200) {
+        message.success("注册成功!");
+        router.push("/user/login");
+        return;
+      } else {
+        message.error(data.message);
+      }
+    });
+  };
+
+  return (
+    <div className={styles.container}>
+      <div className={styles.content}>
+        <div className={styles.top}>
+          <div className={styles.header}>
+            <span className={styles.title}>高潜咨询</span>
+          </div>
+          <div className={styles.desc}>中国企业人效的未来</div>
+          <ProForm
+            form={form}
+            initialValues={{}}
+            submitter={{
+              render: (_, dom) => dom.pop(),
+              submitButtonProps: {
+                size: "large",
+                style: {
+                  width: "100%",
+                },
+              },
+              searchConfig: {
+                submitText: "注册",
+              },
+            }}
+            onFinish={async (values) => {
+              await handleSubmit(values);
+            }}
+          >
+            <>
+              <ProFormText
+                fieldProps={{
+                  size: "large",
+                  prefix: <GoDeviceMobile className={styles.prefixIcon} />,
+                }}
+                required
+                name="phone"
+                placeholder="手机号"
+                rules={[
+                  {
+                    required: true,
+                    message: "请输入手机号!",
+                  },
+                  {
+                    pattern: /^1\d{10}$/,
+                    message: "手机号格式错误!",
+                  },
+                ]}
+              />
+              <ProFormText.Password
+                name="password"
+                fieldProps={{
+                  size: "large",
+                  prefix: <GoLock className={styles.prefixIcon} />,
+                }}
+                required
+                placeholder={"密码"}
+                rules={[
+                  {
+                    required: true,
+                    message: "请输入密码",
+                  },
+                ]}
+              />
+              <ProFormCaptcha
+                fieldProps={{
+                  size: "large",
+                  prefix: <GoLock className={styles.prefixIcon} />,
+                }}
+                captchaProps={{
+                  size: "large",
+                }}
+                placeholder="请输入验证码"
+                captchaTextRender={(timing, count) => {
+                  if (timing) {
+                    return `${count} 获取验证码`;
+                  }
+                  return "获取验证码";
+                }}
+                name="captcha"
+                rules={[
+                  {
+                    required: true,
+                    message: "请输入验证码!",
+                  },
+                ]}
+                onGetCaptcha={async () => {
+                  const phone = form.getFieldValue("phone");
+                  if (!phone) {
+                    message.error("请先输入手机号");
+                    throw new Error("请先输入手机号");
+                  }
+                  const result = await sendCaptcha(phone);
+                  console.log(result);
+                  if (result === false) {
+                    return;
+                  }
+                  message.success("获取验证码成功");
+                }}
+              />
+            </>
+          </ProForm>
+        </div>
+      </div>
+      <div className={styles.footer}>@2024 高潜咨询</div>
+    </div>
+  );
+};
+
+export default Register;

+ 76 - 0
src/app/user/register/register.module.scss

@@ -0,0 +1,76 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  overflow: auto;
+}
+
+.content {
+  flex: 1;
+  margin: 100px 0;
+  padding: 32px 0;
+  width: 328px;
+  margin: 0 auto;
+}
+
+@media screen and (max-width: 767px) {
+  .container {
+    background-position: center 110px;
+    background-size: 100%;
+  }
+
+  .content {
+    margin: 100px 0;
+    padding: 32px 0 24px;
+  }
+}
+
+.icon {
+  margin-left: 8px;
+  color: rgba(0, 0, 0, 0.2);
+  font-size: 24px;
+  vertical-align: middle;
+  cursor: pointer;
+  transition: color 0.3s;
+}
+
+.top {
+  text-align: center;
+  margin-top: 80px;
+}
+
+.header {
+  height: 44px;
+  line-height: 44px;
+  a {
+    text-decoration: none;
+  }
+}
+
+.logo {
+  height: 44px;
+  margin-right: 16px;
+  vertical-align: top;
+}
+
+.title {
+  position: relative;
+  top: 2px;
+  font-weight: 600;
+  font-size: 33px;
+  font-family: Avenir, "Helvetica Neue", Arial, Helvetica, sans-serif;
+}
+
+.desc {
+  margin-top: 12px;
+  margin-bottom: 40px;
+}
+
+.footer {
+  position: absolute;
+  bottom: 0;
+  width: 100%;
+  background-color: #f8f9fa;
+  text-align: center;
+  padding: 1rem 0;
+}

+ 1 - 1
src/components/Footer/footer.module.scss

@@ -64,7 +64,7 @@
             flex: none;
             flex-grow: 0;
             order: 0;
-            width: 97px;
+            width: 110px;
             height: 32px;
 
             /* light-text-color */

+ 52 - 2
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,11 +75,61 @@
       .navbar-register {
         position: absolute;
         top: 0px;
-        left: 313px;
+        left: 413px;
         width: 65px;
         height: 24px;
       }
 
+      .navbar-user {
+        position: absolute;
+        top: 0px;
+        left: 355px;
+        width: 100px;
+        height: 24px;
+        text-align: center;
+
+        .link {
+          color: #ffffff;
+          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(0, 0, 0, 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(0, 0, 0, 0.2);
+          min-width: 100px;
+          z-index: 1000;
+        }
+      }
+      .navbar-user:hover {
+        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+        background-color: rgba(0, 0, 0, 0.2); /* 可选:微弱的背景颜色 */
+      }
+
       .link {
         /* light-text-color */
 

+ 49 - 7
src/components/Header/headerMobile.tsx

@@ -1,9 +1,11 @@
 "use client";
 
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
 import Link from "next/link";
 import Image from "next/image";
 import styles from "./header.module.scss";
+import { useCommonContext } from "@/context/commonContext";
+import Cookies from "js-cookie";
 
 const HeaderMobile: React.FC = () => {
   const [showMenu, setShowMenu] = useState("");
@@ -12,6 +14,22 @@ const HeaderMobile: 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"]}>
@@ -45,15 +63,39 @@ 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>
-            <Link className={styles.item} href={"https://hi-po.com.cn/user/login"}>
-              登录
-            </Link>
-            <Link className={styles.item} href={"https://hi-po.com.cn/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 - 11
src/components/Header/headerPC.tsx

@@ -1,11 +1,46 @@
 "use client";
 
-import React from "react";
+import React, { useState, useEffect } from "react";
 import Link from "next/link";
 import Image from "next/image";
 import styles from "./header.module.scss";
+import { useCommonContext } from "@/context/commonContext";
+import Cookies from "js-cookie";
 
 const HeaderPC: React.FC = () => {
+  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");
+  };
+
   return (
     <div className={styles.nav}>
       <div className={styles["nav-bar"]}>
@@ -29,20 +64,45 @@ 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>
-          <div className={styles["navbar-login"]}>
-            <Link className={styles.link} href={"https://hi-po.com.cn/user/login"}>
-              登录
-            </Link>
-          </div>
-          <div className={styles["navbar-register"]}>
-            <Link className={styles.link} href={"https://hi-po.com.cn/user/register"}>
-              注册
-            </Link>
-          </div>
+          {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 className={styles["nav-container"]}>

+ 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={"https://hi-po.com.cn/user/login"}>
-              登录
-            </Link>
-            <Link className={styles.item} href={"https://hi-po.com.cn/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={"https://hi-po.com.cn/user/login"}>
-              登录
-            </Link>
-            <Link className={styles.link} href={"https://hi-po.com.cn/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>

+ 53 - 0
src/context/commonContext.tsx

@@ -0,0 +1,53 @@
+"use client";
+
+import React, { createContext, useContext, useState, useEffect } from "react";
+import Cookies from "js-cookie";
+import { userInfo } from "@/lib/user";
+
+export interface UserType {
+  id: string;
+  name: string;
+  phone: string;
+}
+
+interface CommonContextType {
+  user: UserType | null;
+  setUser: (user: UserType | null) => void;
+  redirectAfterLogin: string;
+  setRedirectAfterLogin: (path: string) => void;
+}
+
+const CommonContext = createContext<CommonContextType | undefined>(undefined);
+
+export function CommonProvider({ children }: { children: React.ReactNode }) {
+  const [user, setUser] = useState<UserType | null>(null);
+  const [redirectAfterLogin, setRedirectAfterLogin] = useState<string>("/");
+
+  useEffect(() => {
+    const ut = Cookies.get("ut");
+    userInfo(ut).then(({ data: data }) => {
+      if (data) {
+        const currentUser = {
+          id: data.id,
+          name: data.name,
+          phone: data.phone,
+        };
+        setUser(currentUser);
+      }
+    });
+  }, []);
+
+  return (
+    <CommonContext.Provider value={{ user, setUser, redirectAfterLogin, setRedirectAfterLogin }}>
+      {children}
+    </CommonContext.Provider>
+  );
+}
+
+export const useCommonContext = (): CommonContextType => {
+  const context = useContext(CommonContext);
+  if (context === undefined) {
+    throw new Error("useCommon must be used within a CommonProvider");
+  }
+  return context;
+};

+ 1 - 0
src/lib/cms.ts

@@ -34,6 +34,7 @@ export async function fetchArticleList(type: number, page: number, size: number)
   const result = await fetch(`${host}/api/v1/cms/listArticle`, {
     method: "POST",
     body: formData,
+    cache: "no-store",
   });
   const json = await result.json();
   return json;

+ 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;
+}

+ 11 - 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();
@@ -56,3 +57,12 @@ export async function register(params: RegisterParams) {
   const json = await result.json();
   return json;
 }
+
+export async function userInfo(token: string) {
+  const options = {
+    method: "POST",
+  };
+  const response = await fetch(`${host}/api/v1/user/userInfo`, options);
+  const json = await response.json();
+  return json;
+}