├── image.png
├── public
├── logo.png
├── favicon.ico
├── img
│ ├── env.png
│ └── oldCode.jpg
└── images
│ ├── Vue.svg
│ ├── JavaScript.svg
│ ├── webpack.svg
│ ├── HTML.svg
│ ├── CSS.svg
│ ├── weixin.svg
│ ├── github.svg
│ ├── Node.svg
│ ├── ts.svg
│ ├── wechat.svg
│ ├── leetcode.svg
│ ├── React.svg
│ ├── all.svg
│ ├── computer.svg
│ ├── code.svg
│ ├── safe.svg
│ ├── random.svg
│ ├── HTTP.svg
│ ├── tools.svg
│ └── youhua.svg
├── jsconfig.json
├── next.config.js
├── postcss.config.js
├── app
├── create
│ └── page.jsx
├── layout.jsx
├── home
│ └── page.jsx
├── page.jsx
├── category
│ ├── components
│ │ └── SlideBarItem.jsx
│ └── page.jsx
└── random
│ └── page.jsx
├── pages
└── api
│ ├── question.js
│ └── create.js
├── components
├── CardItem.jsx
├── DialogCard.jsx
├── QuestionCard.jsx
└── SlideBar.jsx
├── .gitignore
├── tailwind.config.js
├── package.json
├── README.md
├── styles
└── globals.css
└── lib
└── NotionServer.js
/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dongyuanwai/frontend-interview-konwledge/HEAD/image.png
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dongyuanwai/frontend-interview-konwledge/HEAD/public/logo.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dongyuanwai/frontend-interview-konwledge/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/img/env.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dongyuanwai/frontend-interview-konwledge/HEAD/public/img/env.png
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./*"]
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/img/oldCode.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dongyuanwai/frontend-interview-konwledge/HEAD/public/img/oldCode.jpg
--------------------------------------------------------------------------------
/app/create/page.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function Create() {
4 | return (
5 |
Create
6 | )
7 | }
8 |
9 | export default Create
--------------------------------------------------------------------------------
/pages/api/question.js:
--------------------------------------------------------------------------------
1 | import NotionServer from "../../lib/NotionServer";
2 |
3 |
4 | const notionServer = new NotionServer();
5 |
6 | export default async function handler( req, res ) {
7 | const data = await notionServer.query();
8 | res.status(200).json(data);
9 | }
10 |
--------------------------------------------------------------------------------
/public/images/Vue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/CardItem.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 |
3 | function CardItem({ index,item }) {
4 | return (
5 |
8 | {`${index+1}、${item.title}`}
9 |
10 | )
11 | }
12 |
13 | export default CardItem
--------------------------------------------------------------------------------
/pages/api/create.js:
--------------------------------------------------------------------------------
1 | import NotionServer from "../../lib/NotionServer";
2 |
3 |
4 | const notionServer = new NotionServer();
5 |
6 | export default async function handler( req,res ) {
7 | if (req.method !== "POST") {
8 | res.status(405).send({ message: "Only POST requests allowed" });
9 | return;
10 | }
11 | console.log("添加成功")
12 | const data = await notionServer.create();
13 | res.status(200).json(data);
14 | console.log("添加成功2",data)
15 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 | .env
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
5 | './components/**/*.{js,ts,jsx,tsx,mdx}',
6 | './app/**/*.{js,ts,jsx,tsx,mdx}',
7 | ],
8 | theme: {
9 | extend: {
10 | fontFamily: {
11 | satoshi: ['Satoshi', 'sans-serif'],
12 | inter: ['Inter', 'sans-serif'],
13 | },
14 | colors: {
15 | 'primary-orange': '#FF5722',
16 | }
17 | },
18 | },
19 | plugins: [
20 | require("@tailwindcss/typography")
21 | ],
22 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend-interview-knowledge",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@notionhq/client": "^2.2.7",
13 | "@tailwindcss/typography": "^0.5.9",
14 | "autoprefixer": "10.4.14",
15 | "highlight.js": "^11.8.0",
16 | "markdown-it": "^13.0.1",
17 | "next": "13.4.9",
18 | "postcss": "8.4.25",
19 | "react": "18.2.0",
20 | "react-dom": "18.2.0",
21 | "tailwindcss": "3.3.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/layout.jsx:
--------------------------------------------------------------------------------
1 | import '@/styles/globals.css';
2 | import SlideBar from '@/components/SlideBar';
3 |
4 | export const metadata = {
5 | title: '前端知识点',
6 | description: '前端知识库,前端知识点',
7 | }
8 |
9 | function RootLayout({ children }) {
10 | return (
11 |
12 |
13 |
17 |
18 |
19 | {children}
20 |
21 |
22 |
23 | )
24 | }
25 |
26 |
27 | export default RootLayout
--------------------------------------------------------------------------------
/public/images/JavaScript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/home/page.jsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import React from 'react'
3 |
4 | function page() {
5 | const create=()=>{
6 | const question = {
7 | id:100,
8 | desc:"1212",
9 | category:"",
10 | options:"",
11 | explanation:"这是一个问题的答案",
12 | title:"这是题目",
13 | tag:"JavaScript",
14 | }
15 | fetch("/api/create",{
16 | method: "post",
17 | }).then((res) => {
18 | console.log("res",res)
19 | })
20 | .catch((error) => {
21 | console.error(JSON.stringify(question));
22 | console.error(error);
23 | });
24 | }
25 | return (
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | export default page
--------------------------------------------------------------------------------
/public/images/webpack.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/HTML.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/CSS.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/weixin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 前端知识图谱
2 |
3 |

4 |
短小、精悍、直击要害
5 |
学习、面试必备之良器
6 |
7 |
8 |
9 | ## 安装 markdown-it 和hljs
10 | ```js
11 | npm i markdown-it
12 |
13 | npm i highlight.js
14 | ```
15 | 使用
16 | ```js
17 | import React, { useState,useEffect } from 'react'
18 | // 1. 引入markdown-it库
19 | import markdownIt from 'markdown-it'
20 | import hljs from "highlight.js";
21 | import 'highlight.js/styles/monokai-sublime.css'
22 | // 2. 生成实例对象
23 | const md = new markdownIt({
24 | highlight: function (str, lang) {
25 | if (lang && hljs.getLanguage(lang)) {
26 | try {
27 | return hljs.highlight(str, { language: lang }).value;
28 | } catch (_) {}
29 | }
30 | return ""; // 使用额外的默认转义
31 | },
32 | });
33 |
34 | // 3. 解析markdown语法
35 | const parse = (text: string) => setHtmlString(md.render(text));
36 | useEffect(()=>{
37 | parse()
38 | },[])
39 |
40 |
41 |
45 |
46 | ```
47 |
48 | ## env文件
49 |
50 |
51 |
52 | ## 正在配置多个git账户
53 |
--------------------------------------------------------------------------------
/public/images/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/Node.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/ts.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/wechat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/leetcode.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/DialogCard.jsx:
--------------------------------------------------------------------------------
1 | import { useState,useEffect } from 'react'
2 | // 1. 引入markdown-it库
3 | import markdownIt from 'markdown-it'
4 | import hljs from "highlight.js";
5 | import 'highlight.js/styles/monokai-sublime.css'
6 | // 2. 生成实例对象
7 | const md = new markdownIt({
8 | highlight: function (str, lang) {
9 | if (lang && hljs.getLanguage(lang)) {
10 | try {
11 | return hljs.highlight(str, { language: lang }).value;
12 | } catch (_) {}
13 | }
14 | return ""; // 使用额外的默认转义
15 | },
16 | });
17 |
18 | function DialogCard({data,closeDialog}) {
19 | const [htmlString, setHtmlString] = useState('') // 存储解析后的html字符串
20 |
21 | // 3. 解析markdown语法
22 | const parse = (data) => setHtmlString(md.render(data.explanation));
23 | useEffect(()=>{
24 | parse(data)
25 | },[])
26 |
27 | const handleDialogClick = (e) => {
28 | // 判断点击的元素是否为卡片内容
29 | if (e.target.classList.contains('dialog-close')) {
30 | closeDialog(); // 关闭弹框
31 | }
32 | };
33 | return (
34 |
38 |
39 |
40 | {data.title}
41 |
42 |
48 |
49 |
50 | )
51 | }
52 |
53 | export default DialogCard
--------------------------------------------------------------------------------
/app/page.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { usePathname } from 'next/navigation'
3 | import { useState, useEffect } from "react";
4 | import QuestionCard from "@/components/QuestionCard"
5 |
6 |
7 | function Home() {
8 | const [questionList, setQuestionList] = useState([]);
9 | const [jsList, setJsList] = useState([]);
10 | const [vueList, setVueList] = useState([]);
11 | const [reactList, setReactList] = useState([]);
12 | const [httpList, setHttpList] = useState([]);
13 | const getQuestionList = () => {
14 | fetch('/api/question')
15 | .then((res) => res.json())
16 | .then((res) => {
17 | if (res) {
18 | setQuestionList(res.sort((a, b) => a.id - b.id));
19 | }
20 | })
21 | .catch((error) => {
22 | console.error(error);
23 | });
24 | };
25 |
26 | useEffect(() => {
27 | getQuestionList();
28 | }, []);
29 |
30 | useEffect(() => {
31 | const jsItems = questionList.filter(item => item.tags === "JavaScript");
32 | const vueItems = questionList.filter(item => item.tags === "Vue");
33 | const reactItems = questionList.filter(item => item.tags === "React");
34 | const httpItems = questionList.filter(item => item.tags === "HTTP");
35 | setJsList(jsItems);
36 | setVueList(vueItems);
37 | setReactList(reactItems);
38 | setHttpList(httpItems);
39 | }, [questionList]);
40 | return (
41 |
42 |
49 |
50 | )
51 | }
52 |
53 | export default Home
--------------------------------------------------------------------------------
/components/QuestionCard.jsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { useState, useEffect } from "react"
3 | import Image from "next/image"
4 | import CardItem from "./CardItem"
5 | import DialogCard from './DialogCard'
6 |
7 | function QuestionCard({questionList,type}) {
8 | const [showDialog,setShowDialog] = useState(false)
9 | const [currentQuestion,setCurrentQuestion] = useState({})
10 | const handClick = (item)=>{
11 | console.log("点击了item",item)
12 | setCurrentQuestion(item)
13 | setShowDialog(true)
14 | }
15 | const closeDialog = (e)=>{
16 | setShowDialog(false)
17 | }
18 |
19 | return (
20 |
24 |
26 |
27 |
33 |
{type}
34 |
35 |
36 | {questionList&&questionList.map((item,index) => (
37 |
handClick(item)}
39 | key={item.id}
40 | >
41 |
45 |
46 | ))}
47 |
48 | {
49 | showDialog&&(
50 |
closeDialog(e)}
53 | />
54 | )
55 | }
56 |
57 | )
58 | }
59 |
60 | export default QuestionCard
--------------------------------------------------------------------------------
/public/images/React.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap");
2 |
3 | @tailwind base;
4 | @tailwind components;
5 | @tailwind utilities;
6 |
7 | .main {
8 | width: 100vw;
9 | min-height: 100vh;
10 | position: fixed;
11 | display: flex;
12 | justify-content: center;
13 | padding: 120px 24px 160px 10px;
14 | pointer-events: none;
15 | }
16 |
17 | .main:before {
18 | background: radial-gradient(circle, rgba(2, 0, 36, 0) 0, #fafafa 100%);
19 | position: absolute;
20 | content: "";
21 | width: 100%;
22 | height: 100%;
23 | top: 0;
24 | }
25 |
26 | .main:after {
27 | content: "";
28 | background-image: url("/assets/images/grid.svg");
29 | position: absolute;
30 | width: 100%;
31 | height: 100%;
32 | top: 0;
33 | opacity: 0.4;
34 | filter: invert(1);
35 | }
36 |
37 | .gradient {
38 | height: fit-content;
39 | width: 100%;
40 | max-width: 640px;
41 | background-image: radial-gradient(
42 | at 27% 37%,
43 | hsla(215, 98%, 61%, 1) 0px,
44 | transparent 0%
45 | ),
46 | radial-gradient(at 97% 21%, hsla(125, 98%, 72%, 1) 0px, transparent 50%),
47 | radial-gradient(at 52% 99%, hsla(354, 98%, 61%, 1) 0px, transparent 50%),
48 | radial-gradient(at 10% 29%, hsla(256, 96%, 67%, 1) 0px, transparent 50%),
49 | radial-gradient(at 97% 96%, hsla(38, 60%, 74%, 1) 0px, transparent 50%),
50 | radial-gradient(at 33% 50%, hsla(222, 67%, 73%, 1) 0px, transparent 50%),
51 | radial-gradient(at 79% 53%, hsla(343, 68%, 79%, 1) 0px, transparent 50%);
52 | position: absolute;
53 | content: "";
54 | width: 100%;
55 | height: 100%;
56 | filter: blur(100px) saturate(150%);
57 | top: 80px;
58 | opacity: 0.15;
59 | }
60 |
61 | @media screen and (max-width: 640px) {
62 | .main {
63 | padding: 0;
64 | }
65 | }
66 |
67 | .app {
68 | @apply relative flex w-[100vw] h-[100vh] overflow-hidden;
69 | }
70 |
71 |
72 | .show::-webkit-scrollbar {
73 | width: 10px;
74 | height: 10px;
75 | }
76 |
77 | .show::-webkit-scrollbar-thumb {
78 | background: #ccc;
79 | border-radius: 5px;
80 | }
81 |
82 |
--------------------------------------------------------------------------------
/app/category/components/SlideBarItem.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useEffect, useState } from 'react'
3 | import Image from 'next/image'
4 | import { useRouter ,useSearchParams} from 'next/navigation';
5 |
6 | const SlideItem = ({currentQuestionList,setCurrentQuestion}) => {
7 | const [activeTabId, setactiveTabId] = useState()
8 | const question_id = useSearchParams().get('name');
9 | const tagId = useSearchParams().get('tagId');
10 | const router = useRouter();
11 | const handleClick = (item)=>{
12 | setCurrentQuestion(item)
13 | setactiveTabId(item._id)
14 | router.push(`/category?tagId=${tagId}&name=${item._id}`)
15 | }
16 | useEffect(()=>{
17 | if(!currentQuestionList.list||currentQuestionList.list.length==0) return
18 | const _questions = currentQuestionList.list.filter((item)=>item._id ==question_id )
19 | if(question_id&&_questions.length>0){
20 | handleClick(_questions[0])
21 | }
22 | },[])
23 | return (
24 |
25 | {/* LOGO */}
26 |
27 |
33 |
{currentQuestionList.type}
34 |
35 | {/* 导航 */}
36 |
37 | {currentQuestionList?.list?.map((item,index) => (
38 |
handleClick(item)}
43 | >
44 |
47 | {`${index+1}、${item.title}`}
48 |
49 |
50 |
51 | ))}
52 |
53 |
54 | )
55 | }
56 |
57 | export default SlideItem
--------------------------------------------------------------------------------
/public/images/all.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/computer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/code.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/NotionServer.js:
--------------------------------------------------------------------------------
1 | import { Client } from "@notionhq/client";
2 | const auth = process.env.NOTION_AUTH;
3 | const database = process.env.NOTION_DATABASE_ID;
4 | export default class NotionService {
5 | constructor() {
6 | this.client = new Client({ auth });
7 | }
8 | async query() {
9 | const response = await this.client.databases.query({
10 | database_id: database,
11 | });
12 |
13 | // return response.results;
14 | return response.results.map((item) => transformer(item));
15 | }
16 |
17 |
18 | async create(){
19 | const question = {
20 | id:90,
21 | desc:"1212",
22 | category:"",
23 | options:"",
24 | explanation:"这是一个问题的答案",
25 | title:"这是题目",
26 | tag:"JavaScript",
27 | }
28 |
29 | const properties = {
30 | desc: {
31 | type: "rich_text",
32 | rich_text: strToArray(question.desc ?? "", 2000, []),
33 | },
34 | category: {
35 | type: "select",
36 | select: {
37 | name: question.category,
38 | },
39 | },
40 | options: {
41 | type: "rich_text",
42 | rich_text: strToArray(question.options ?? "", 2000, []),
43 | },
44 | explanation: {
45 | type: "rich_text",
46 | rich_text: strToArray(question.explanation ?? "", 2000, []),
47 | },
48 | title: {
49 | type: "title",
50 | title: [
51 | {
52 | type: "text",
53 | text: {
54 | content: question.title ?? "",
55 | },
56 | },
57 | ],
58 | },
59 | };
60 |
61 | if (question.tag) {
62 | properties.tag = {
63 | type: "relation",
64 | relation: [
65 | {
66 | id: question.tag,
67 | },
68 | ],
69 | };
70 | }
71 | const response = await this.client.databases.create({
72 | parent: {
73 | database_id: database,
74 | },
75 | properties,
76 | });
77 | return response;
78 | }
79 |
80 | }
81 |
82 | function strToArray(
83 | str,
84 | count,
85 | arr
86 | ){
87 | arr.push({
88 | type: "text",
89 | text: {
90 | content: str.slice(0, count),
91 | },
92 | });
93 | str = str.slice(count);
94 | if (str.length >= count) {
95 | return strToArray(str, count, arr);
96 | } else {
97 | return arr;
98 | }
99 | }
100 |
101 | function transformer(page) {
102 | let data = {};
103 |
104 | for (const key in page.properties) {
105 | switch (page.properties[key].type) {
106 | case "relation":
107 | data[key] = page.properties[key].relation[0]?page.properties[key].relation[0].id:"";
108 | break;
109 |
110 | case "title":
111 | case "rich_text":
112 | let content="";
113 | page.properties[key][page.properties[key].type].map((item)=>{
114 | if(item){
115 | content+=item.text.content
116 | }
117 | })
118 | data[key] = content
119 | break;
120 | case "id":
121 | data[key] =
122 | page.properties[key];
123 | break;
124 |
125 | default:
126 | data[key] = page.properties[key].unique_id.number;
127 | break;
128 | }
129 | }
130 |
131 | return data;
132 | }
--------------------------------------------------------------------------------
/public/images/safe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/random.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/SlideBar.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useEffect, useState } from 'react'
3 | import Link from 'next/link'
4 | import Image from 'next/image'
5 | import { usePathname, useSearchParams } from 'next/navigation'
6 | const Nav = () => {
7 | const [slidItems, setSlidItems] = useState([
8 | {
9 | tagId: 1,
10 | type: "总览",
11 | icon: "all",
12 | href: "/",
13 | list: []
14 | }, {
15 | tagId: 12,
16 | type: "HTML",
17 | icon: "HTML",
18 | href: "/category?tagId=12",
19 | list: []
20 | }, {
21 | tagId: 11,
22 | type: "CSS",
23 | icon: "CSS",
24 | href: "/category?tagId=11",
25 | list: []
26 | }, {
27 | tagId: 10,
28 | type: "JavaScript",
29 | icon: "JavaScript",
30 | href: "/category?tagId=10",
31 | list: []
32 | }, {
33 | tagId: 14,
34 | type: "Vue",
35 | icon: "Vue",
36 | href: "/category?tagId=14",
37 | list: []
38 | }, {
39 | tagId: 13,
40 | type: "React",
41 | icon: "React",
42 | href: "/category?tagId=13",
43 | list: []
44 | }, {
45 | tagId: 21,
46 | type: "每日随机",
47 | icon: "random",
48 | href: "/random?tagId=21",
49 | list: []
50 | },
51 | ])
52 |
53 | // pathname 获取的是路由
54 | const pathname = usePathname()
55 | // useSearchParams().get('tagId') 获取的是参数tagId 的值
56 | const tagId = useSearchParams().get('tagId');
57 | const [activeTabId, setactiveTabId] = useState()
58 |
59 | useEffect(() => {
60 | console.log("tagId-=-=", tagId)
61 | if (tagId) {
62 | setactiveTabId(tagId)
63 | } else {
64 | setactiveTabId(1)
65 | }
66 | }, [])
67 | return (
68 |
69 |
106 |
107 |
108 |
114 |
扫码关注
115 |
获取更多面试解析
116 |
117 |
118 |
119 |
125 |
126 |
127 |
128 |
134 |
135 |
136 |
137 |
138 | )
139 | }
140 |
141 | export default Nav
--------------------------------------------------------------------------------
/app/random/page.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useState, useEffect } from "react";
3 | // import SlideItem from "./components/SlideBarItem";
4 | import { useSearchParams } from 'next/navigation'
5 | import feList from "@/public/fe_interview.json"
6 |
7 | // 1. 引入markdown-it库
8 | import markdownIt from 'markdown-it'
9 | import hljs from "highlight.js";
10 | // import "highlight.js/styles/default.css"; // 或者选择其他样式,默认使用default.css
11 | import 'highlight.js/styles/monokai-sublime.css'
12 |
13 | // 2. 生成实例对象
14 | const md = new markdownIt({
15 | highlight: function (str, lang) {
16 | if (lang && hljs.getLanguage(lang)) {
17 | try {
18 | return hljs.highlight(str, { language: lang }).value;
19 | } catch (_) { }
20 | }
21 | return ""; // 使用额外的默认转义
22 | },
23 | });
24 |
25 | function Random() {
26 | const [questionList, setQuestion] = useState({});
27 | const [currentQuestion, setCurrentQuestion] = useState({});
28 |
29 | const [htmlString, setHtmlString] = useState('') // 存储解析后的html字符串
30 | const searchParams = useSearchParams()
31 | let _tagId = searchParams.get('tagId')
32 | // 3. 解析markdown语法
33 | const parse = (data) => setHtmlString(md.render(data));
34 | // 处理数据
35 | const handleData = () => {
36 | let infolist = [];
37 | const questionMap = {
38 | 10: {
39 | tagId: 10,
40 | type: "JavaScript",
41 | list: []
42 | }, 11: {
43 | tagId: 11,
44 | type: "CSS",
45 | list: []
46 | }, 12: {
47 | tagId: 12,
48 | type: "HTML",
49 | list: []
50 | }, 13: {
51 | tagId: 13,
52 | type: "React",
53 | list: []
54 | }, 14: {
55 | tagId: 14,
56 | type: "Vue",
57 | list: []
58 | }, 18: {
59 | tagId: 18,
60 | type: "Node",
61 | list: []
62 | }, 19: {
63 | tagId: 19,
64 | type: "TypeScript",
65 | list: []
66 | }, 23: {
67 | tagId: 23,
68 | type: "小程序",
69 | list: []
70 | }
71 | }
72 | feList.map((item) => {
73 | if (questionMap[item.tagId]) {
74 | infolist.push(item)
75 | }
76 | })
77 |
78 | const currentDate = new Date();
79 | // 获取年份
80 | const year = currentDate.getFullYear();
81 | // 获取月份,注意 getMonth() 返回的是 0-11,需要加 1
82 | const month = currentDate.getMonth() + 1;
83 | // 获取日期
84 | const day = currentDate.getDate();
85 | // 输出结果
86 | const currentDay = `${year}-${month}-${day}`
87 | console.log(`当前日期:${currentDay}`, localStorage.getItem('today'));
88 |
89 | let hasSelectList = JSON.parse(localStorage.getItem('hasSelect_list')) || []
90 | if (localStorage.getItem('today') != currentDay) {
91 | localStorage.setItem('today', currentDay);
92 | let randomNumber = Math.floor(Math.random() * (infolist.length-1)); // 假设生成的随机数在 0-999 内
93 | while (hasSelectList.indexOf(randomNumber) != -1) { // 如果随机数已经出现过,继续生成新的随机数
94 | randomNumber = Math.floor(Math.random() * (infolist.length-1));
95 | }
96 | hasSelectList.push(randomNumber);
97 | localStorage.setItem('current_index', randomNumber);
98 | localStorage.setItem('hasSelect_list', JSON.stringify(hasSelectList));
99 | }
100 | setCurrentQuestion(()=>infolist[localStorage.getItem('current_index')])
101 | }
102 | useEffect(() => {
103 | handleData()
104 | }, []);
105 |
106 | useEffect(() => {
107 | parse(currentQuestion.explanation || '')
108 | }, [currentQuestion])
109 |
110 | return (
111 |
112 | {currentQuestion && (
113 |
114 |
117 |
118 | {currentQuestion.title}
119 |
120 |
124 |
125 |
126 | )}
127 |
128 | )
129 | }
130 |
131 | export default Random
--------------------------------------------------------------------------------
/app/category/page.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useState, useEffect } from "react";
3 | import SlideItem from "./components/SlideBarItem";
4 | import { useSearchParams } from 'next/navigation'
5 | import feList from "@/public/fe_interview.json"
6 |
7 | // 1. 引入markdown-it库
8 | import markdownIt from 'markdown-it'
9 | import hljs from "highlight.js";
10 | // import "highlight.js/styles/default.css"; // 或者选择其他样式,默认使用default.css
11 | import 'highlight.js/styles/monokai-sublime.css'
12 |
13 | // 2. 生成实例对象
14 | const md = new markdownIt({
15 | highlight: function (str, lang) {
16 | if (lang && hljs.getLanguage(lang)) {
17 | try {
18 | return hljs.highlight(str, { language: lang }).value;
19 | } catch (_) { }
20 | }
21 | return ""; // 使用额外的默认转义
22 | },
23 | });
24 |
25 | function Category() {
26 | const [questionList, setQuestionList] = useState({});
27 | const [currentQuestionList, setCurrentQuestionList] = useState({});
28 | const [currentQuestion, setCurrentQuestion] = useState({});
29 |
30 | const [htmlString, setHtmlString] = useState('') // 存储解析后的html字符串
31 | const searchParams = useSearchParams()
32 | let _tagId = searchParams.get('tagId')
33 | // 3. 解析markdown语法
34 | const parse = (data) => setHtmlString(md.render(data));
35 | // 处理数据
36 | const handleData = (data) => {
37 | const info = new Set();
38 | const questionMap = {
39 | 10: {
40 | tagId: 10,
41 | type: "JavaScript",
42 | list: []
43 | }, 11: {
44 | tagId: 11,
45 | type: "CSS",
46 | list: []
47 | }, 12: {
48 | tagId: 12,
49 | type: "HTML",
50 | list: []
51 | }, 13: {
52 | tagId: 13,
53 | type: "React",
54 | list: []
55 | }, 14: {
56 | tagId: 14,
57 | type: "Vue",
58 | list: []
59 | }, 17: {
60 | tagId: 17,
61 | type: "趣味题",
62 | list: []
63 | }, 18: {
64 | tagId: 18,
65 | type: "Node",
66 | list: []
67 | }, 19: {
68 | tagId: 19,
69 | type: "TypeScript",
70 | list: []
71 | }, 20: {
72 | tagId: 20,
73 | type: "性能优化",
74 | list: []
75 | }, 21: {
76 | tagId: 21,
77 | type: "前端安全",
78 | list: []
79 | }, 32: {
80 | tagId: 32,
81 | type: "选择题",
82 | list: []
83 | }, 26: {
84 | tagId: 26,
85 | type: "编程题",
86 | list: []
87 | }, 31: {
88 | tagId: 31,
89 | type: "leetcode",
90 | list: []
91 | }, 30: {
92 | tagId: 30,
93 | type: "计算机基础",
94 | list: []
95 | }, 27: {
96 | tagId: 27,
97 | type: "设计模式",
98 | list: []
99 | }, 23: {
100 | tagId: 23,
101 | type: "小程序",
102 | list: []
103 | }, 28: {
104 | tagId: 28,
105 | type: "工程化",
106 | list: []
107 | }, 29: {
108 | tagId: 29,
109 | type: "工具",
110 | list: []
111 | }, 100: {
112 | tagId: 100,
113 | type: "其他",
114 | list: []
115 | },
116 | }
117 | //
118 | data.map((item) => {
119 | info.add(item.tagId);
120 | if (questionMap[item.tagId]) {
121 | questionMap[item.tagId].list.push(item)
122 | } else {
123 | questionMap[100].list.push(item)
124 | }
125 | })
126 | setQuestionList(() => questionMap)
127 | }
128 | useEffect(() => {
129 | // 本地数据
130 | handleData(feList)
131 | return
132 | // 服务器数据
133 | fetch('http://localhost:8686/getAll')
134 | .then((res) => res.json())
135 | .then((res) => {
136 | if (res) {
137 | console.log("获取全部数据几点几分",res)
138 | if(res.status == 200){
139 | handleData(res.data)
140 |
141 | }
142 | // setQuestionList(res.sort((a, b) => a.id - b.id));
143 | }
144 | })
145 | .catch((error) => {
146 | console.error(error);
147 | });
148 |
149 | }, []);
150 | useEffect(() => {
151 | setCurrentQuestionList(() => questionList[_tagId])
152 | }, [questionList, _tagId]);
153 |
154 |
155 | useEffect(() => {
156 | parse(currentQuestion.explanation || '')
157 | }, [currentQuestion])
158 |
159 | return (
160 |
161 | {currentQuestionList && (
162 |
163 |
167 |
170 |
171 | {currentQuestion.title}
172 |
173 |
177 |
178 |
179 | )}
180 |
181 | )
182 | }
183 |
184 | export default Category
--------------------------------------------------------------------------------
/public/images/HTTP.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/tools.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/public/images/youhua.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------