├── 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 | file_type_vue -------------------------------------------------------------------------------- /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 |
14 |
15 |
16 |
17 |
18 | 19 |
{children}
20 |
21 | 22 | 23 | ) 24 | } 25 | 26 | 27 | export default RootLayout -------------------------------------------------------------------------------- /public/images/JavaScript.svg: -------------------------------------------------------------------------------- 1 | file_type_js_official -------------------------------------------------------------------------------- /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 | file_type_webpack -------------------------------------------------------------------------------- /public/images/HTML.svg: -------------------------------------------------------------------------------- 1 | file_type_html -------------------------------------------------------------------------------- /public/images/CSS.svg: -------------------------------------------------------------------------------- 1 | file_type_css -------------------------------------------------------------------------------- /public/images/weixin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前端知识图谱 2 |
3 | logo 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 | file_type_node -------------------------------------------------------------------------------- /public/images/ts.svg: -------------------------------------------------------------------------------- 1 | file_type_typescript_official -------------------------------------------------------------------------------- /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 |
43 |
47 |
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 |
44 | 45 | 46 | 47 | 48 |
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 | jslogo 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 | img 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 | img 114 |

扫码关注

115 |

获取更多面试解析

116 |
117 |
118 | 119 | img 125 | 126 | 127 | 128 | img 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 |
121 |
122 |
123 |
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 |
174 |
175 |
176 |
177 |
178 |
179 | )} 180 |
181 | ) 182 | } 183 | 184 | export default Category -------------------------------------------------------------------------------- /public/images/HTTP.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/tools.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/images/youhua.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------