diff --git a/public/logo.svg b/public/logo.svg
new file mode 100644
index 0000000..c96dc20
--- /dev/null
+++ b/public/logo.svg
@@ -0,0 +1,33 @@
+
+
\ No newline at end of file
diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx
new file mode 100644
index 0000000..6771c80
--- /dev/null
+++ b/src/components/Header/index.tsx
@@ -0,0 +1,148 @@
+'use client';
+
+import React, { useEffect, useState } from 'react';
+import { Box, AppBar, Stack, Link, Button } from '@mui/material';
+
+import Search from './search';
+
+const NAV_LINK = [
+ {
+ title: '行业百科',
+ href: '/v3/wiki',
+ },
+ {
+ title: '技术博客',
+ href: '/v3/blog',
+ },
+ {
+ title: '在线工具',
+ href: '/tools',
+ target: '_blank',
+ },
+ {
+ title: '漏洞情报',
+ href: '/v3/vuldb',
+ },
+];
+
+const Header = () => {
+ const [user, setUser] = useState(null);
+
+ useEffect(() => {
+ fetch('/api/v1/user/profile', {
+ credentials: 'include',
+ }).then((res) => {
+ res.json().then((data) => {
+ setUser(data);
+ });
+ });
+ }, []);
+
+ return (
+
+
+
+ {
+ window.open('/', '_self');
+ }}
+ sx={{ ml: 5, mr: 10, cursor: 'pointer' }}
+ />
+
+
+ {NAV_LINK.map((item) => (
+ {
+ e.preventDefault();
+ window.open(item.href, '_self');
+ }}
+ sx={{
+ fontSize: '16px',
+ color: '#041B0F',
+ fontWeight: 700,
+ '&:hover': {
+ color: 'primary.main',
+ },
+ }}
+ >
+ {item.title}
+
+ ))}
+
+
+
+
+
+ {user ? (
+
+
+
+ ) : (
+ <>
+
+
+ >
+ )}
+
+
+
+ );
+};
+
+export default Header;
diff --git a/src/components/Header/search.tsx b/src/components/Header/search.tsx
new file mode 100644
index 0000000..4231bfd
--- /dev/null
+++ b/src/components/Header/search.tsx
@@ -0,0 +1,325 @@
+'use client';
+
+import React, { useState } from 'react';
+import {
+ Box,
+ InputBase,
+ Button,
+ createTheme,
+ ThemeProvider,
+ Dialog,
+ styled,
+ InputBaseProps,
+ BoxProps,
+ ButtonProps,
+ SvgIconProps,
+ Stack,
+ List,
+ ListItem,
+ ListItemButton,
+ ListItemText,
+ Typography,
+ IconButton,
+ Popover,
+} from '@mui/material';
+import SearchRoundedIcon from '@mui/icons-material/SearchRounded';
+import ClearRoundedIcon from '@mui/icons-material/ClearRounded';
+import { useLocalStorageState } from 'ahooks';
+import { useRouter } from 'next/navigation';
+
+const innerTheme = createTheme({
+ palette: {
+ primary: {
+ main: '#1A191C',
+ },
+ },
+});
+
+const SearchBaseInput = styled(InputBase)(({ theme }) => ({
+ flex: 1,
+ padding: '0 30px',
+ height: '40px',
+ fontSize: 20,
+ border: '1px solid',
+ borderColor: 'transparent',
+ transition: 'all 0.3s',
+ borderTopLeftRadius: '4px',
+ borderBottomLeftRadius: '4px',
+ borderRight: 'none',
+ '&:hover': {
+ borderColor: theme.palette.primary.main,
+ },
+ '&:focus-within': {
+ borderColor: theme.palette.primary.main,
+ },
+}));
+
+const SearchBaseWrapper = styled(Box)(({ theme }) => {
+ return {
+ display: 'flex',
+ alignItems: 'center',
+ width: 360,
+ height: 40,
+ borderRadius: '4px',
+ transition: 'all 0.3s',
+ boxShadow: '0px 0px 20px 0px rgba(0,28,85,0.1)',
+ backgroundColor: '#fff',
+ '&:hover': {
+ boxShadow: '0px 10px 40px 0px rgba(0,28,85,0.15)',
+ },
+ };
+});
+
+const SearchBaseButton = styled(Button)({
+ width: 82,
+ height: 40,
+ borderRadius: '0px 4px 4px 0px',
+});
+
+export const SearchBase = React.forwardRef(
+ (
+ props: {
+ type?: 'icon' | 'button' | 'icon-button';
+ open?: boolean;
+ setOpen?(open: boolean): void;
+ SearchBaseWrapperProps?: BoxProps;
+ SearchBaseInputProps?: InputBaseProps;
+ SearchBaseButtonProps?: ButtonProps;
+ SearchRoundedIconProps?: SvgIconProps;
+ keywords?: string;
+ },
+ ref
+ ) => {
+ const {
+ type = 'button',
+ setOpen,
+ SearchBaseWrapperProps,
+ SearchBaseInputProps,
+ SearchBaseButtonProps,
+ SearchRoundedIconProps,
+ keywords = '',
+ } = props;
+ return type === 'icon-button' ? (
+ {
+ setOpen?.(true);
+ }}
+ />
+ ) : (
+ {
+ setOpen?.(true);
+ }}
+ {...SearchBaseWrapperProps}
+ >
+ }
+ {...SearchBaseInputProps}
+ />
+ {type === 'button' && (
+
+
+ 搜索
+
+
+ )}
+
+ );
+ }
+);
+SearchBase.displayName = 'SearchBase';
+
+export const DialogSearch = (props: { open: boolean; onClose(): void }) => {
+ const { open, onClose } = props;
+ const [keywords, setKeywords] = useState('');
+ const router = useRouter();
+ const [recentSearch, setRecentSearch] = useLocalStorageState(
+ 'recent-search',
+ {
+ defaultValue: [],
+ }
+ );
+
+ const onSearch = () => {
+ if (!keywords) {
+ return;
+ }
+ onClose();
+ if (recentSearch) {
+ const index = recentSearch.indexOf(keywords);
+ if (index > -1) {
+ recentSearch.splice(index, 1);
+ recentSearch.unshift(keywords);
+ } else {
+ if (recentSearch.length >= 10) {
+ recentSearch.pop();
+ }
+ recentSearch.unshift(keywords);
+ }
+ setRecentSearch([...recentSearch]);
+ } else {
+ setRecentSearch([keywords]);
+ }
+ router.push(`/v3/s?keywords=${keywords}`);
+ };
+
+ return (
+
+ );
+};
+
+export const PopoverContent = React.forwardRef(() => {
+ return (
+
+ The content of the Popover.
+
+ );
+});
+PopoverContent.displayName = 'PopoverContent';
+
+const Search = React.forwardRef(
+ (
+ props: {
+ type?: 'icon' | 'button' | 'icon-button';
+ SearchBaseWrapperProps?: BoxProps;
+ SearchBaseInputProps?: InputBaseProps;
+ SearchBaseButtonProps?: ButtonProps;
+ SearchRoundedIconProps?: SvgIconProps;
+ },
+ ref
+ ) => {
+ const [open, setOpen] = useState(false);
+ const {
+ type = 'button',
+ SearchBaseWrapperProps,
+ SearchBaseInputProps,
+ SearchBaseButtonProps,
+ SearchRoundedIconProps,
+ } = props;
+ const onClose = () => {
+ setOpen(false);
+ };
+ return (
+ <>
+
+ {/* */}
+
+ >
+ );
+ }
+);
+
+Search.displayName = 'Search';
+export default Search;
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index b6b51a1..e6a0cff 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -20,7 +20,7 @@ import { usePath } from '@/hooks';
import { useMemo } from 'react';
import { allTools } from '@/utils/tools';
import Head from 'next/head';
-import { Header as RiverHeader } from '@chaitin_rivers/multi_river';
+import RiverHeader from '../components/Header';
import '@chaitin_rivers/excalidraw/index.css';
const clientSideEmotionCache = createEmotionCache();
@@ -64,8 +64,15 @@ export default function App({
- {isProduction ? : null}
-
+ {isProduction ? : null}
+