diff --git a/src/App.tsx b/src/App.tsx index 869499d..3b7738a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import React from 'react' import router from '@/router' -import LoadingMask from '@/components/common/LoadingMask.tsx' +import LoadingMask from '@/components/common/LoadingMask' const App: React.FC = () => { return ( diff --git a/src/AuthRoute.tsx b/src/AuthRoute.tsx index 788ba52..7335e5e 100644 --- a/src/AuthRoute.tsx +++ b/src/AuthRoute.tsx @@ -1,20 +1,41 @@ import { getLoginStatus } from '@/utils/auth.ts' +import { PRODUCTION_NAME } from '@/constants/Common.constants.ts' const AuthRoute = () => { - const match = useMatches()[1] - const handle = match.handle as RouteHandle + const matches = useMatches() + const lastMatch = matches.reduce((_, second) => second) + const handle = lastMatch.handle as RouteHandle + const location = useLocation() const outlet = useOutlet() const isLogin = getLoginStatus() return useMemo(() => { - if (handle?.auth && !isLogin) { - return + document.title = `${handle?.titlePrefix ?? ''}${ + handle?.title ? handle?.title : PRODUCTION_NAME + }${handle?.titlePostfix ?? ''}` + if (matches.some(({ handle }) => (handle as RouteHandle)?.auth) && !isLogin) { + return ( + + ) } - if (isLogin && match.pathname === '/login') { + if (isLogin && lastMatch.pathname === '/login') { return } return outlet - }, [handle?.auth, isLogin, match.pathname, outlet]) + }, [ + handle?.title, + handle?.titlePostfix, + handle?.titlePrefix, + isLogin, + lastMatch.pathname, + location.search, + matches, + outlet + ]) } export default AuthRoute diff --git a/src/ant-design.d.ts b/src/ant-design.d.ts new file mode 100644 index 0000000..5ec575f --- /dev/null +++ b/src/ant-design.d.ts @@ -0,0 +1,8 @@ +import * as React from 'react' +import { CustomIconComponentProps } from '@ant-design/icons/es/components/Icon' + +declare global { + type IconComponent = + | React.ComponentType> + | React.ForwardRefExoticComponent +} diff --git a/src/assets/css/common.scss b/src/assets/css/common.scss index e1716f7..650a829 100644 --- a/src/assets/css/common.scss +++ b/src/assets/css/common.scss @@ -25,8 +25,8 @@ height: 100%; } -.background-white { - background-color: white; +.background-origin { + background-color: constants.$origin-color; } .center-box { @@ -107,9 +107,11 @@ } .flex-horizontal { + display: flex; flex-direction: row; } .flex-vertical { + display: flex; flex-direction: column; } \ No newline at end of file diff --git a/src/assets/css/components/common/hide-scrollbar.scss b/src/assets/css/components/common/hide-scrollbar.scss index 799a993..2cf545e 100644 --- a/src/assets/css/components/common/hide-scrollbar.scss +++ b/src/assets/css/components/common/hide-scrollbar.scss @@ -1,6 +1,7 @@ @use '@/assets/css/constants' as constants; .hide-scrollbar-mask { + position: relative; width: 100%; height: 100%; overflow: hidden; @@ -14,7 +15,6 @@ .hide-scrollbar-content { display: inline-block; width: 100%; - min-width: 900px; } } @@ -52,17 +52,25 @@ .vertical-scrollbar { padding: 12px 4px; - width: 16px; height: 100%; - right: 0; + left: 100%; top: 0; + transform: translateX(-100%); + + .box { + width: 8px; + } } .horizontal-scrollbar { padding: 4px 12px; width: 100%; - height: 16px; left: 0; - bottom: 0; + top: 100%; + transform: translateY(-100%); + + .box { + height: 8px; + } } } \ No newline at end of file diff --git a/src/assets/css/components/home/footer.scss b/src/assets/css/components/home/footer.scss index 7abf726..551c276 100644 --- a/src/assets/css/components/home/footer.scss +++ b/src/assets/css/components/home/footer.scss @@ -1,19 +1,21 @@ +@use "@/assets/css/constants" as constants; + .icons { display: flex; gap: 20px; .icon { font-size: 8em; - color: white; + color: constants.$origin-color; } } .links { font-size: 2em; text-decoration: underline; - color: white; + color: constants.$origin-color; > * { - color: white; + color: constants.$origin-color; } } \ No newline at end of file diff --git a/src/assets/css/constants.scss b/src/assets/css/constants.scss index edf5734..18e8338 100644 --- a/src/assets/css/constants.scss +++ b/src/assets/css/constants.scss @@ -1,8 +1,16 @@ -$main-color: #00D4FF; +$origin-color: white; +$main-color: #4E47BB; +$secondary-color: #BAB8E5; +$active-color: #EBECFFD; $background-color: #F5F5F5; $font-main-color: #4D4D4D; $font-secondary-color: #9E9E9E; $focus-color: #DDDDDD; $border-color: rgba(204, 204, 204, 0.33); $url-color: rgba(102, 102, 102, .8); -$url-active-color: #ccc \ No newline at end of file +$url-active-color: #ccc; +$SIZE_ICON_XS: 16px; +$SIZE_ICON_SM: 20px; +$SIZE_ICON_MD: 24px; +$SIZE_ICON_LG: 32px; +$SIZE_ICON_XL: 64px; \ No newline at end of file diff --git a/src/assets/css/pages/header.scss b/src/assets/css/pages/home-framework.scss similarity index 96% rename from src/assets/css/pages/header.scss rename to src/assets/css/pages/home-framework.scss index 836974e..34717e0 100644 --- a/src/assets/css/pages/header.scss +++ b/src/assets/css/pages/home-framework.scss @@ -8,7 +8,7 @@ z-index: 1; width: 100%; height: 70px; - background-color: white; + background-color: constants.$origin-color; border: { bottom: { width: 1px; @@ -99,7 +99,7 @@ position: absolute; width: 100%; text-align: center; - background-color: white; + background-color: constants.$origin-color; overflow: hidden; .item { @@ -171,7 +171,7 @@ color: constants.$border-color; style: solid; }; - background-color: white; + background-color: constants.$origin-color; z-index: 1; ul { diff --git a/src/assets/css/pages/login.scss b/src/assets/css/pages/login.scss index 22629a8..988f60f 100644 --- a/src/assets/css/pages/login.scss +++ b/src/assets/css/pages/login.scss @@ -1,3 +1,5 @@ +@use "@/assets/css/constants" as constants; + .login-background { display: flex; height: 100vh; @@ -24,7 +26,7 @@ .login-box-left-text { font-size: 3rem; - color: white; + color: constants.$origin-color; font-weight: bold; > div:last-child { @@ -37,7 +39,7 @@ position: relative; height: 100%; flex: 3; - background-color: white; + background-color: constants.$origin-color; } .login-from-text { diff --git a/src/assets/css/pages/tools-framework.scss b/src/assets/css/pages/tools-framework.scss new file mode 100644 index 0000000..b95691f --- /dev/null +++ b/src/assets/css/pages/tools-framework.scss @@ -0,0 +1,247 @@ +@use "@/assets/css/constants" as constants; +@use "@/assets/css/mixins" as mixins; + +body { + background-color: constants.$background-color; +} + +.left-panel { + display: flex; + flex-direction: column; + width: clamp(180px, 20vw, 240px); + background-color: constants.$origin-color; + user-select: none; + transition: all .3s; + white-space: nowrap; + overflow: hidden; + + .title { + display: flex; + align-items: center; + font-weight: bold; + padding: 10px 14px; + color: constants.$main-color; + + .icon-box { + display: flex; + justify-content: center; + align-items: center; + padding: 10px; + width: 40px; + height: 40px; + font-size: constants.$SIZE_ICON_SM; + border-radius: 8px; + cursor: pointer; + span { + transform: rotateZ(180deg); + transition: all .3s; + } + + &:hover { + background-color: constants.$background-color; + } + } + + .text { + flex: 1; + font-size: 2em; + text-align: center; + letter-spacing: 0.6em; + transform: translateX(0.3em); + } + } + + .content { + display: flex; + min-height: 0; + flex-direction: column; + flex: 1; + + .toolsMenu { + min-height: 0; + flex: 1; + } + + ul { + > li { + &.item { + position: relative; + margin: 4px 14px; + font-size: 1.4em; + + .menu-bt { + border-radius: 8px; + overflow: hidden; + height: 40px; + + .icon-box { + display: flex; + justify-content: center; + align-items: center; + padding: 0 10px; + width: 40px; + height: 40px; + font-size: constants.$SIZE_ICON_SM; + cursor: pointer; + } + + a { + display: flex; + align-items: center; + height: 100%; + width: 100%; + transition: all 0.2s; + + .text { + flex: 1; + padding-left: 8px; + } + + &.active { + color: constants.$origin-color; + background-color: constants.$main-color !important; + } + } + } + + .submenu { + display: none; + position: fixed; + padding-left: 20px; + z-index: 10000; + + .content { + display: flex; + flex-direction: column; + gap: 2px; + padding: 10px 10px; + background-color: constants.$origin-color; + border-radius: 8px; + + .item { + border-radius: 8px; + white-space: nowrap; + overflow: hidden; + + a { + display: block; + padding: 8px 16px; + transition: all 0.2s; + + &.active { + color: constants.$origin-color; + background-color: constants.$main-color !important; + } + } + + &:hover a { + background-color: constants.$background-color; + } + } + } + } + + &:hover { + .menu-bt { + a { + background-color: constants.$background-color; + } + } + + .submenu { + display: block; + animation: 0.3s ease; + @include mixins.unique-keyframes { + 0% { + display: block; + transform: translateX(-10px); + opacity: 0; + } + 100% { + display: block; + transform: translateX(0); + opacity: 1; + } + } + } + } + } + } + } + } + + .separate { + height: 0; + margin: 10px 5px; + border: { + width: 1px; + color: constants.$font-secondary-color; + style: solid; + }; + opacity: 0.4; + } + + .footer { + display: flex; + align-items: center; + font-weight: bold; + padding: 8px 14px; + color: constants.$main-color; + + .icon-box { + display: flex; + justify-content: center; + align-items: center; + margin-left: 4px; + padding: 10px; + width: 32px; + height: 32px; + font-size: constants.$SIZE_ICON_XS; + border: 2px constants.$font-secondary-color solid; + color: constants.$font-secondary-color; + border-radius: 50%; + cursor: pointer; + } + + .text { + flex: 1; + padding-left: 8px; + font-size: 1.4em; + color: constants.$font-main-color; + user-select: text; + } + } + + &.hide { + width: 68px; + + .title { + .icon-box { + span { + transform: rotateZ(360deg); + transition: all .3s; + } + } + .text { + display: none; + } + } + + .menu-bt { + .text { + display: none; + } + } + + .footer { + .text { + display: none; + } + } + } +} + +.right-panel { + flex: 1; + background-color: constants.$background-color; +} \ No newline at end of file diff --git a/src/assets/svg/down.svg b/src/assets/svg/down.svg index 872d226..587e1d1 100644 --- a/src/assets/svg/down.svg +++ b/src/assets/svg/down.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/assets/svg/expand.svg b/src/assets/svg/expand.svg new file mode 100644 index 0000000..95e5060 --- /dev/null +++ b/src/assets/svg/expand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/github.svg b/src/assets/svg/github.svg index fdf2ea5..02c1634 100644 --- a/src/assets/svg/github.svg +++ b/src/assets/svg/github.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/assets/svg/home.svg b/src/assets/svg/home.svg new file mode 100644 index 0000000..ee81985 --- /dev/null +++ b/src/assets/svg/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/jenkins.svg b/src/assets/svg/jenkins.svg index 51fbbc6..ee77645 100644 --- a/src/assets/svg/jenkins.svg +++ b/src/assets/svg/jenkins.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/assets/svg/loading.svg b/src/assets/svg/loading.svg index a7c3d67..140bea2 100644 --- a/src/assets/svg/loading.svg +++ b/src/assets/svg/loading.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/assets/svg/logo.svg b/src/assets/svg/logo.svg index 243d161..bae41f6 100644 --- a/src/assets/svg/logo.svg +++ b/src/assets/svg/logo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/assets/svg/menu.svg b/src/assets/svg/menu.svg index 46ac707..f506c1b 100644 --- a/src/assets/svg/menu.svg +++ b/src/assets/svg/menu.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/assets/svg/user.svg b/src/assets/svg/user.svg new file mode 100644 index 0000000..1ac062a --- /dev/null +++ b/src/assets/svg/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/common/FitCenter.tsx b/src/components/common/FitCenter.tsx index b33d8e6..7ada721 100644 --- a/src/components/common/FitCenter.tsx +++ b/src/components/common/FitCenter.tsx @@ -11,7 +11,7 @@ const FitCenter: React.FC = (props) => { return ( <>
((props, ref return ( <>
((props, ref) => { @@ -125,6 +130,9 @@ const HideScrollbar = forwardRef((prop options?: boolean | EventListenerOptions ): void { rootRef.current?.removeEventListener(type, listener, options) + }, + refreshLayout(): void { + refreshLayout() } } }, @@ -138,6 +146,7 @@ const HideScrollbar = forwardRef((prop const lastScrollbarClickPositionRef = useRef({ x: -1, y: -1 }) const lastScrollbarTouchPositionRef = useRef({ x: -1, y: -1 }) const lastTouchPositionRef = useRef({ x: -1, y: -1 }) + const [refreshTime, setRefreshTime] = useState(0) const [verticalScrollbarWidth, setVerticalScrollbarWidth] = useState(0) const [verticalScrollbarLength, setVerticalScrollbarLength] = useState(100) const [verticalScrollbarPosition, setVerticalScrollbarPosition] = useState(0) @@ -157,6 +166,10 @@ const HideScrollbar = forwardRef((prop isHiddenVerticalScrollbarWhenFull, isShowHorizontalScrollbar, isHiddenHorizontalScrollbarWhenFull, + minWidth, + minHeight, + scrollbarWidth, + animationTransitionTime, ..._props } = props @@ -363,24 +376,32 @@ const HideScrollbar = forwardRef((prop ) } + const refreshLayout = () => { + setRefreshTime(Date.now()) + } + useEffect(() => { const windowResizeListener = () => { - setVerticalScrollbarWidth( - (rootRef.current?.offsetWidth ?? 0) - (rootRef.current?.clientWidth ?? 0) - ) - setHorizontalScrollbarWidth( - (rootRef.current?.offsetHeight ?? 0) - (rootRef.current?.clientHeight ?? 0) - ) - - rootRef.current && - setVerticalScrollbarLength( - (rootRef.current.clientHeight / (contentRef.current?.clientHeight ?? 0)) * 100 + setTimeout(() => { + setVerticalScrollbarWidth( + (rootRef.current?.offsetWidth ?? 0) - (rootRef.current?.clientWidth ?? 0) + ) + setHorizontalScrollbarWidth( + (rootRef.current?.offsetHeight ?? 0) - (rootRef.current?.clientHeight ?? 0) ) - rootRef.current && - setHorizontalScrollbarLength( - (rootRef.current.clientWidth / (contentRef.current?.clientWidth ?? 0)) * 100 - ) + rootRef.current && + setVerticalScrollbarLength( + (rootRef.current.clientHeight / (contentRef.current?.clientHeight ?? 0)) * + 100 + ) + + rootRef.current && + setHorizontalScrollbarLength( + (rootRef.current.clientWidth / (contentRef.current?.clientWidth ?? 0)) * 100 + ) + refreshLayout() + }, animationTransitionTime) return windowResizeListener } @@ -425,6 +446,7 @@ const HideScrollbar = forwardRef((prop window.removeEventListener('resize', windowResizeListener) } }, [ + animationTransitionTime, horizontalScrollbarLength, isPreventAnyScroll, isPreventHorizontalScroll, @@ -457,8 +479,8 @@ const HideScrollbar = forwardRef((prop className={'hide-scrollbar-selection'} tabIndex={0} style={{ - width: `calc(100vw + ${verticalScrollbarWidth}px)`, - height: `calc(100vh + ${horizontalScrollbarWidth}px)`, + width: `calc(${maskRef.current?.clientWidth}px + ${verticalScrollbarWidth}px)`, + height: `calc(${maskRef.current?.clientHeight}px + ${horizontalScrollbarWidth}px)`, touchAction: isPreventAnyScroll ? 'none' : '', msTouchAction: isPreventAnyScroll ? 'none' : '' }} @@ -469,7 +491,12 @@ const HideScrollbar = forwardRef((prop onTouchMove={isPreventAnyScroll ? handleDefaultTouchmove : undefined} onScroll={handleDefaultScroll} > -
+
{props.children}
@@ -477,11 +504,18 @@ const HideScrollbar = forwardRef((prop hidden={ !isShowVerticalScrollbar || ((isHiddenVerticalScrollbarWhenFull ?? true) && - verticalScrollbarLength === 100) + verticalScrollbarLength >= 100) } className={'scrollbar vertical-scrollbar'} + style={{ + height: maskRef.current ? maskRef.current?.clientHeight - 1 : undefined, + top: maskRef.current?.clientTop, + left: maskRef.current + ? maskRef.current?.clientLeft + maskRef.current?.clientWidth - 1 + : undefined + }} > -
+
((prop hidden={ !isShowHorizontalScrollbar || ((isHiddenHorizontalScrollbarWhenFull ?? true) && - horizontalScrollbarLength === 100) + horizontalScrollbarLength >= 100) } className={'scrollbar horizontal-scrollbar'} + style={{ + width: maskRef.current ? maskRef.current?.clientWidth - 1 : undefined, + left: maskRef.current?.clientLeft, + top: maskRef.current + ? maskRef.current?.clientTop + maskRef.current?.clientHeight - 1 + : undefined + }} > -
+
= (props) => { return (
  • diff --git a/src/components/home/Footer.tsx b/src/components/home/Footer.tsx index 47c8f62..2200e30 100644 --- a/src/components/home/Footer.tsx +++ b/src/components/home/Footer.tsx @@ -1,8 +1,8 @@ import React from 'react' -import FitCenter from '@/components/common/FitCenter.tsx' +import FitCenter from '@/components/common/FitCenter' import Icon from '@ant-design/icons' import '@/assets/css/components/home/footer.scss' -import FitFullScreen from '@/components/common/FitFullScreen.tsx' +import FitFullScreen from '@/components/common/FitFullScreen' import { NavLink } from 'react-router-dom' const Footer: React.FC = () => { diff --git a/src/components/home/OxygenToolbox.tsx b/src/components/home/OxygenToolbox.tsx index 6423e3b..576bd5b 100644 --- a/src/components/home/OxygenToolbox.tsx +++ b/src/components/home/OxygenToolbox.tsx @@ -1,5 +1,5 @@ import React from 'react' -import FitCenter from '@/components/common/FitCenter.tsx' +import FitCenter from '@/components/common/FitCenter' const OxygenToolbox: React.FC = () => { return ( diff --git a/src/components/home/Slogan.tsx b/src/components/home/Slogan.tsx index 0c32a6e..e0439c0 100644 --- a/src/components/home/Slogan.tsx +++ b/src/components/home/Slogan.tsx @@ -1,7 +1,7 @@ import React from 'react' import Icon from '@ant-design/icons' import '@/assets/css/components/home/slogan.scss' -import FitCenter from '@/components/common/FitCenter.tsx' +import FitCenter from '@/components/common/FitCenter' interface SloganProps { onClickScrollDown: (event: React.MouseEvent) => void diff --git a/src/vite-env.d.ts b/src/global.d.ts similarity index 59% rename from src/vite-env.d.ts rename to src/global.d.ts index 324bb5f..e64304b 100644 --- a/src/vite-env.d.ts +++ b/src/global.d.ts @@ -1,4 +1,5 @@ /// +/// interface ImportMetaEnv { readonly VITE_API_URL: string @@ -9,10 +10,28 @@ interface ImportMeta { readonly env: ImportMetaEnv } +type ToolsJsonObject = { + path: string + id: string + component?: React.ComponentType + name?: string + titlePrefix?: string + title?: string + titlePostfix?: string + icon?: IconComponent + menu?: boolean + auth?: boolean + children?: ToolsJsonObject[] +} + type RouteHandle = { name?: string menu?: boolean auth?: boolean + titlePrefix?: string + title?: string + titlePostfix?: string + icon?: IconComponent } type _Response = { diff --git a/src/main.tsx b/src/main.tsx index e102176..0464d85 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client' import zh_CN from 'antd/locale/zh_CN' import '@/assets/css/base.scss' import '@/assets/css/common.scss' -import App from './App.tsx' +import App from './App' ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/src/pages/MainFramework.tsx b/src/pages/HomeFramework.tsx similarity index 91% rename from src/pages/MainFramework.tsx rename to src/pages/HomeFramework.tsx index c867634..56993c1 100644 --- a/src/pages/MainFramework.tsx +++ b/src/pages/HomeFramework.tsx @@ -1,5 +1,5 @@ import React from 'react' -import '@/assets/css/pages/header.scss' +import '@/assets/css/pages/home-framework.scss' import router from '@/router' import LoadingMask from '@/components/common/LoadingMask' import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar' @@ -7,7 +7,7 @@ import Icon from '@ant-design/icons' import { COLOR_FONT_SECONDARY } from '@/constants/Common.constants.ts' import { NavLink } from 'react-router-dom' -export const MainFrameworkContext = createContext<{ +export const HomeFrameworkContext = createContext<{ navbarHiddenState: { navbarHidden: boolean setNavbarHidden: (newValue: boolean) => void @@ -53,7 +53,7 @@ export const MainFrameworkContext = createContext<{ hideScrollbarRef: createRef() }) -const MainFramework: React.FC = () => { +const HomeFramework: React.FC = () => { const routeId = useMatches()[1].id const routeChildren = router.routes[0].children?.find((value) => value.id === routeId)?.children @@ -81,10 +81,11 @@ const MainFramework: React.FC = () => { ref={hideScrollbarRef} isPreventVerticalScroll={preventScroll} isShowHorizontalScrollbar={true} + minWidth={'900px'} >
    -
    +
    FatWeb @@ -115,11 +116,9 @@ const MainFramework: React.FC = () => { key={subRoute.id} > { })}
    {
    -
    +
      {routeChildren?.map((route) => { return ( @@ -187,7 +184,7 @@ const MainFramework: React.FC = () => {
    - { > - +
    ) } -export default MainFramework +export default HomeFramework diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 02ff4d2..92a8bac 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -12,6 +12,7 @@ import '@/assets/css/pages/login.scss' const Login: React.FC = () => { const [messageApi, contextHolder] = message.useMessage() const navigate = useNavigate() + const [searchParams] = useSearchParams() const [isLoggingIn, setIsLoggingIn] = useState(false) const onFinish = (values: LoginForm) => { @@ -25,7 +26,11 @@ const Login: React.FC = () => { setToken(data?.token ?? '') void messageApi.success('登录成功') setTimeout(() => { - navigate('/') + if (searchParams.has('redirect')) { + navigate(searchParams.get('redirect') ?? '/') + } else { + navigate('/') + } }, 1500) break case SYSTEM_USERNAME_NOT_FOUND: diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx new file mode 100644 index 0000000..fb3c76d --- /dev/null +++ b/src/pages/ToolsFramework.tsx @@ -0,0 +1,197 @@ +import React from 'react' +import FitFullScreen from '@/components/common/FitFullScreen' +import '@/assets/css/pages/tools-framework.scss' +import Icon from '@ant-design/icons' +import { toolsJsonObjects } from '@/router/tools.tsx' +import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar.tsx' +import { getLocalStorage, setLocalStorage } from '@/utils/common.ts' + +const ToolsFramework: React.FC = () => { + const hideScrollbarRef = useRef(null) + const [submenuTop, setSubmenuTop] = useState(0) + const [submenuLeft, setSubmenuLeft] = useState(0) + const [hideSidebar, setHideSidebar] = useState(getLocalStorage('hideSidebar') === 'false') + + const switchSidebar = () => { + setHideSidebar(!hideSidebar) + setLocalStorage('hideSidebar', hideSidebar ? 'true' : 'false') + setTimeout(() => { + hideScrollbarRef.current?.refreshLayout() + }, 300) + } + + const showSubmenu = (e: React.MouseEvent) => { + const parentElement = e.currentTarget.parentElement + if (parentElement && parentElement.childElementCount === 2) { + const parentClientRect = parentElement.getBoundingClientRect() + if (parentClientRect.top <= screen.height / 2) { + setSubmenuTop(parentClientRect.top) + } else { + setSubmenuTop( + parentClientRect.top - + (parentElement.lastElementChild?.clientHeight ?? 0) + + e.currentTarget.clientHeight + ) + } + setSubmenuLeft(parentClientRect.right) + } + } + + return ( + <> + +
    +
    + + + + 氮工具 +
    +
    +
    +
      +
    • +
      + + isPending ? 'pending' : isActive ? 'active' : '' + } + > +
      + {toolsJsonObjects[0].icon ? ( + + ) : undefined} +
      + {toolsJsonObjects[0].name} +
      +
      +
    • +
    • +
      + + isPending ? ' pending' : isActive ? ' active' : '' + } + > +
      + {toolsJsonObjects[1].icon ? ( + + ) : undefined} +
      + {toolsJsonObjects[1].name} +
      +
      +
    • +
    • +
      +
    • +
    +
    + +
      + {toolsJsonObjects.map((tool) => { + return tool.menu && + tool.id !== 'tools' && + tool.id !== 'tools-all' ? ( +
    • +
      + + isPending + ? 'pending' + : isActive + ? 'active' + : '' + } + > +
      + {tool.icon ? ( + + ) : undefined} +
      + {tool.name} +
      +
      + {tool.children ? ( +
        +
        + {tool.children.map((subTool) => { + return subTool.menu ? ( +
      • + + isPending + ? 'pending' + : isActive + ? 'active' + : '' + } + > + + {subTool.name} + + +
      • + ) : undefined + })} +
        +
      + ) : undefined} +
    • + ) : undefined + })} +
    +
    +
    +
    +
    +
    + + + + 未登录 +
    +
    +
    + + + ) +} + +export default ToolsFramework diff --git a/src/pages/Home.tsx b/src/pages/home/index.tsx similarity index 97% rename from src/pages/Home.tsx rename to src/pages/home/index.tsx index 3091de0..25ca34d 100644 --- a/src/pages/Home.tsx +++ b/src/pages/home/index.tsx @@ -1,7 +1,7 @@ import React from 'react' import '@/assets/css/components/home/home.scss' import FitFullScreen from '@/components/common/FitFullScreen' -import { MainFrameworkContext } from '@/pages/MainFramework' +import { HomeFrameworkContext } from '@/pages/HomeFramework' import Slogan from '@/components/home/Slogan' import OxygenToolbox from '@/components/home/OxygenToolbox' import Indicator from '@/components/common/Indicator' @@ -13,7 +13,7 @@ const Home: React.FC = () => { navbarHiddenState: { navbarHidden, setNavbarHidden }, showDropdownMenuState: { setShowDropdownMenu }, preventScrollState: { setPreventScroll } - } = useContext(MainFrameworkContext) + } = useContext(HomeFrameworkContext) const fitFullScreenRef = useRef(null) const scrollTimeout = useRef(0) diff --git a/src/pages/tools/All.tsx b/src/pages/tools/All.tsx new file mode 100644 index 0000000..85e812d --- /dev/null +++ b/src/pages/tools/All.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +const All: React.FC = () => { + return <> +} + +export default All diff --git a/src/pages/Tools.tsx b/src/pages/tools/index.tsx similarity index 100% rename from src/pages/Tools.tsx rename to src/pages/tools/index.tsx diff --git a/src/router/index.tsx b/src/router/index.tsx index 580eaf6..ea360d9 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -1,4 +1,5 @@ import React from 'react' +import tools from '@/router/tools' const routes: RouteObject[] = [ { @@ -15,15 +16,26 @@ const routes: RouteObject[] = [ id: 'loading', Component: React.lazy(() => import('@/components/common/LoadingMask')) }, + { + path: '/tools', + id: 'toolsFramework', + Component: React.lazy(() => import('@/pages/ToolsFramework')), + children: tools, + handle: { + name: '工具', + title: '工具', + auth: false + } + }, { path: '', - id: 'mainFramework', - Component: React.lazy(() => import('@/pages/MainFramework')), + id: 'homeFramework', + Component: React.lazy(() => import('@/pages/HomeFramework')), children: [ { path: '', id: 'home', - Component: React.lazy(() => import('@/pages/Home')), + Component: React.lazy(() => import('@/pages/home')), handle: { name: '主页', menu: true, @@ -32,33 +44,28 @@ const routes: RouteObject[] = [ }, { path: 'https://blog.fatweb.top', - id: 'blog', + id: 'url-blog', handle: { name: '博客', - menu: true, - auth: false + menu: true } }, { - path: 'tools', - id: 'tools', - Component: React.lazy(() => import('@/pages/Tools')), + path: '/tools', + id: 'url-tools', children: [ { path: 'translation', - id: 'tools-translation', - Component: React.lazy(() => import('@/pages/tools/Translation')), + id: 'url-tools-translation', handle: { name: '翻译', - menu: true, - auth: false + menu: true } } ], handle: { name: '工具', - menu: true, - auth: false + menu: true } } ] diff --git a/src/router/tools.tsx b/src/router/tools.tsx new file mode 100644 index 0000000..a98995c --- /dev/null +++ b/src/router/tools.tsx @@ -0,0 +1,312 @@ +import React from 'react' + +const defaultTitle = '氮工具' + +export const toolsJsonObjects: ToolsJsonObject[] = [ + { + path: '', + id: 'tools', + component: React.lazy(() => import('@/pages/tools')), + icon: React.lazy(() => import('~icons/fatweb/logo.jsx')), + name: '主页', + menu: true, + auth: false + }, + { + path: 'all', + id: 'tools-all', + component: React.lazy(() => import('@/pages/tools')), + name: '全部工具', + titlePostfix: ' - 全部工具', + icon: React.lazy(() => import('~icons/fatweb/logo.jsx')), + menu: true, + auth: false + }, + { + path: 'translation', + id: 'tools-translation', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false, + children: [ + { + path: '1', + id: '1', + name: '翻译1', + icon: React.lazy(() => import('~icons/fatweb/logo.jsx')), + menu: true, + auth: false + }, + { + path: '2', + id: '2', + name: '翻译2', + menu: true, + auth: false + } + ] + }, + { + path: 'translation-', + id: 'tools-translation-', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译-', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false, + children: [ + { + path: '1-', + id: '1-', + name: '翻译1-', + menu: true, + auth: false + }, + { + path: '2-', + id: '2-', + name: '翻译2-', + menu: true, + auth: false + } + ] + }, + { + path: 'translation--', + id: 'tools-translation--', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--1', + id: 'tools-translation--1', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--1', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--2', + id: 'tools-translation--2', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--2', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--3', + id: 'tools-translation--3', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--3', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--4', + id: 'tools-translation--4', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--4', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--5', + id: 'tools-translation--5', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--5', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--6', + id: 'tools-translation--6', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--6', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--7', + id: 'tools-translation--7', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--7', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--8', + id: 'tools-translation--8', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--8', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--9', + id: 'tools-translation--9', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--9', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--10', + id: 'tools-translation--10', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--10', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--1-', + id: 'tools-translation--1-', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--1-', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--2-', + id: 'tools-translation--2-', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--2-', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--3-', + id: 'tools-translation--3-', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--3-', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--4-', + id: 'tools-translation--4-', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--4-', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--5-', + id: 'tools-translation--5-', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--5-', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--6-', + id: 'tools-translation--6-', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--6-', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--7-', + id: 'tools-translation--7-', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--7-', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--8-', + id: 'tools-translation--8-', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--8-', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--9-', + id: 'tools-translation--9-', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--9-', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false + }, + { + path: 'translation--10-', + id: 'tools-translation--10-', + component: React.lazy(() => import('@/pages/tools/Translation')), + name: '翻译--10-', + icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), + menu: true, + auth: false, + children: [ + { + path: '1-1', + id: '1-1', + name: '翻译1-', + menu: true, + auth: false + }, + { + path: '2-1', + id: '2-1', + name: '翻译2-', + menu: true, + auth: false + } + ] + } +] + +const tools: RouteObject[] = toolsJsonObjects.map((value) => ({ + path: value.path, + id: value.id, + Component: value.component, + handle: { + name: value.name, + titlePrefix: value.titlePrefix, + title: value.title ?? defaultTitle, + titlePostfix: value.titlePostfix, + icon: value.icon, + menu: value.menu, + auth: value.auth + }, + children: value.children?.map((value) => ({ + path: value.path, + id: value.id, + Component: value.component, + handle: { + name: value.name, + titlePrefix: value.titlePrefix, + title: value.title ?? defaultTitle, + titlePostfix: value.titlePostfix, + icon: value.icon, + menu: value.menu, + auth: value.auth + } + })) +})) + +export default tools diff --git a/src/utils/common.ts b/src/utils/common.ts index 96f1784..7d0f9ce 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -18,14 +18,14 @@ export function setCookie( daysToLive: number | null, path: string | null ): void { - let cookie = name + '=' + encodeURIComponent(value) + let cookie = `${name}=${encodeURIComponent(value)}` if (typeof daysToLive === 'number') { cookie = `${cookie}; max-age=${daysToLive * 24 * 60 * 60}` } if (typeof path === 'string') { - cookie += '; path=' + path + cookie = `${cookie}; path=${path}` } document.cookie = cookie @@ -61,7 +61,7 @@ export function getToken(): string | null { } export function removeCookie(name: string): void { - document.cookie = name + '=; max-age=0' + document.cookie = `${name}=; max-age=0` } export function removeLocalStorage(name: string): void {