From 9d74dce8b1dec592bb07d3f1d2f1db273faac416 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Tue, 26 Sep 2023 18:29:34 +0800 Subject: [PATCH 001/307] Add tools menu and submenu --- src/assets/css/pages/header.scss | 33 ++++++++++++++++-- .../home/index.tsx => pages/Home.tsx} | 2 +- src/pages/MainFramework.tsx | 27 ++++++++++++++- src/pages/Tools.tsx | 7 ++++ src/pages/tools/Translation.tsx | 7 ++++ src/router/index.tsx | 34 ++++++++++++++++++- 6 files changed, 105 insertions(+), 5 deletions(-) rename src/{components/home/index.tsx => pages/Home.tsx} (98%) create mode 100644 src/pages/Tools.tsx create mode 100644 src/pages/tools/Translation.tsx diff --git a/src/assets/css/pages/header.scss b/src/assets/css/pages/header.scss index eedd195..98ff714 100644 --- a/src/assets/css/pages/header.scss +++ b/src/assets/css/pages/header.scss @@ -50,6 +50,7 @@ .item { display: inline-block; + position: relative; font-size: 1.5em; transition: { property: all; @@ -72,9 +73,37 @@ }; } - :hover { + .item:hover { transform: translateY(-5px); } + + .item:hover .submenu { + display: block; + } + + .submenu { + display: none; + position: absolute; + width: 100%; + text-align: center; + background-color: white; + transform: translateY(4px); + border: { + width: 1px; + color: constants.$border-color; + style: solid; + }; + + .item { + display: block; + padding: 10px; + font-size: 0.8em; + } + + .item:hover { + transform: none; + } + } } .dropdown-menu-button { @@ -144,7 +173,7 @@ } } -@media screen and (max-width: 900px){ +@media screen and (max-width: 900px) { .dropdown-menu-content.show { display: block; } diff --git a/src/components/home/index.tsx b/src/pages/Home.tsx similarity index 98% rename from src/components/home/index.tsx rename to src/pages/Home.tsx index 0598636..3091de0 100644 --- a/src/components/home/index.tsx +++ b/src/pages/Home.tsx @@ -4,7 +4,7 @@ import FitFullScreen from '@/components/common/FitFullScreen' import { MainFrameworkContext } from '@/pages/MainFramework' import Slogan from '@/components/home/Slogan' import OxygenToolbox from '@/components/home/OxygenToolbox' -import Indicator from '@/components/common/Indicator.tsx' +import Indicator from '@/components/common/Indicator' import Footer from '@/components/home/Footer' const Home: React.FC = () => { diff --git a/src/pages/MainFramework.tsx b/src/pages/MainFramework.tsx index 24bcc24..bcf29ea 100644 --- a/src/pages/MainFramework.tsx +++ b/src/pages/MainFramework.tsx @@ -90,7 +90,7 @@ const MainFramework: React.FC = () => { -
+
    {routeChildren?.map((route) => { return ( diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index 7c2ef8b..a0dff56 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -1,13 +1,38 @@ import React from 'react' import FitFullScreen from '@/components/common/FitFullScreen' import '@/assets/css/pages/tools-framework.scss' -import router from '@/router' import Icon from '@ant-design/icons' +import { toolsJsonObjects } from '@/router/tools.tsx' +import _ from 'lodash' const ToolsFramework: React.FC = () => { - const frameworkRoute = useMatches()[1] - const routeId = frameworkRoute.id - const routeChildren = router.routes[0].children?.find((value) => value.id === routeId)?.children + const location = useLocation() + + const [multipleMenuShown, setMultipleMenuShown] = useState( + toolsJsonObjects.map((value) => ({ + id: value.id, + path: value.path, + shown: `${location.pathname}/`.startsWith(`/tools/${value.path}/`) + })) + ) + + useEffect(() => { + const temp = _.clone(multipleMenuShown) + temp.forEach((value) => { + value.shown = `${location.pathname}/`.startsWith(`/tools/${value.path}/`) + }) + setMultipleMenuShown(temp) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [location]) + + const switchSubmenu = (menuId: string) => { + return () => { + const temp = _.clone(multipleMenuShown) + const menu = temp.find(({ id }) => id === menuId) + menu && (menu.shown = !menu.shown) + setMultipleMenuShown(temp) + } + } return ( <> @@ -20,92 +45,93 @@ const ToolsFramework: React.FC = () => {
  • - - isPending ? 'pending' : isActive ? 'active' : '' - } - > - {routeChildren ? ( - <> - - - {(routeChildren[0].handle as RouteHandle).name} - - - ) : ( - '主页' - )} - +
    + + isPending ? 'pending' : isActive ? 'active' : '' + } + > + + {toolsJsonObjects[0].name} + +
  • - - isPending ? ' pending' : isActive ? ' active' : '' - } - > - {routeChildren ? ( - <> - - - {(routeChildren[1].handle as RouteHandle).name} - - - ) : ( - '全部工具' - )} - +
    + + isPending ? ' pending' : isActive ? ' active' : '' + } + > + + {toolsJsonObjects[1].name} + +
  • - {routeChildren?.map((route) => { - return (route.handle as RouteHandle).menu && - route.id !== 'tools' && - route.id !== 'tools-all' ? ( + {toolsJsonObjects.map((tool) => { + return tool.menu && + tool.id !== 'tools' && + tool.id !== 'tools-all' ? (
  • id === tool.id + )?.shown ?? false + ? ' show' + : '' + }` + : 'item' + } + key={tool.id} > - - isPending ? 'pending' : isActive ? 'active' : '' - } - > - {(route.handle as RouteHandle).icon ? ( - +
    + {tool.children ? ( +
    + +
    ) : undefined} - - {(route.handle as RouteHandle).name} - - - {route.children ? ( + + isPending ? 'pending' : isActive ? 'active' : '' + } + > + {tool.children ? undefined : tool.icon ? ( + + ) : undefined} + {tool.name} + +
    + {tool.children ? (
      - {route.children.map((subRoute) => { - return (subRoute.handle as RouteHandle).menu ? ( -
    • + {tool.children.map((subTool) => { + return subTool.menu ? ( +
    • { : '' } > - {(subRoute.handle as RouteHandle) - .icon ? ( + {subTool.icon ? ( ) : undefined} - { - ( - subRoute.handle as RouteHandle - ).name - } + {subTool.name}
    • diff --git a/src/router/tools.tsx b/src/router/tools.tsx index 43ea3f6..5c675d2 100644 --- a/src/router/tools.tsx +++ b/src/router/tools.tsx @@ -1,6 +1,8 @@ import React from 'react' -const toolsJsonObjects: ToolsJsonObject[] = [ +const defaultTitle = '氮工具' + +export const toolsJsonObjects: ToolsJsonObject[] = [ { path: '', id: 'tools', @@ -15,6 +17,7 @@ const toolsJsonObjects: ToolsJsonObject[] = [ id: 'tools-all', component: React.lazy(() => import('@/pages/tools')), name: '全部工具', + titlePostfix: ' - 全部工具', icon: React.lazy(() => import('~icons/fatweb/logo.jsx')), menu: true, auth: false @@ -86,6 +89,9 @@ const tools: RouteObject[] = toolsJsonObjects.map((value) => ({ 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 @@ -96,6 +102,9 @@ const tools: RouteObject[] = toolsJsonObjects.map((value) => ({ 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 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 { From ac3c1dbc2c460e59bdcf916eb3e7fd37a89a1f00 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Thu, 12 Oct 2023 18:34:14 +0800 Subject: [PATCH 021/307] Optimize submenu in ToolsFramework --- .../css/components/common/hide-scrollbar.scss | 1 - src/assets/css/pages/tools-framework.scss | 118 ++++---- src/components/common/HideScrollbar.tsx | 9 +- src/pages/HomeFramework.tsx | 1 + src/pages/ToolsFramework.tsx | 267 ++++++++---------- src/router/tools.tsx | 182 +++++++++++- 6 files changed, 367 insertions(+), 211 deletions(-) diff --git a/src/assets/css/components/common/hide-scrollbar.scss b/src/assets/css/components/common/hide-scrollbar.scss index 799a993..5a9f7c9 100644 --- a/src/assets/css/components/common/hide-scrollbar.scss +++ b/src/assets/css/components/common/hide-scrollbar.scss @@ -14,7 +14,6 @@ .hide-scrollbar-content { display: inline-block; width: 100%; - min-width: 900px; } } diff --git a/src/assets/css/pages/tools-framework.scss b/src/assets/css/pages/tools-framework.scss index 4534112..e9a54d0 100644 --- a/src/assets/css/pages/tools-framework.scss +++ b/src/assets/css/pages/tools-framework.scss @@ -1,4 +1,5 @@ @use "@/assets/css/constants" as constants; +@use "@/assets/css/mixins" as mixins; body { background-color: constants.$background-color; @@ -21,16 +22,24 @@ body { .content { > ul { > li { - margin: 4px 14px; &.item { + position: relative; + margin: 4px 14px; font-size: 1.4em; - border-radius: 8px; - overflow: hidden; .menu-bt { - .icon { - margin-right: 16px; + border-radius: 8px; + overflow: hidden; + + .icon-box { + cursor: pointer; + width: 26px; + height: 26px; + + .icon { + margin-right: 16px; + } } a { @@ -38,6 +47,7 @@ body { padding: 8px 16px; height: 100%; width: 100%; + transition: all 0.2s; .text { flex: 1; @@ -50,54 +60,60 @@ body { } } + .submenu { + display: none; + position: fixed; + padding-left: 20px; + left: 100%; + top: 0; + + .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 { background-color: constants.$background-color; - } - } - &.multiple-item { - font-size: 1.4em; - border-radius: 8px; - overflow: hidden; - - .menu-bt { - display: flex; - - .icon-box { - cursor: pointer; - padding: 8px 16px; - - .icon { - transition: all 0.3s ease; - transform: rotate(180deg); - } - } - - a { - display: flex; - padding: { - top: 8px; - bottom: 8px; - right: 16px; - }; - height: 100%; - width: 100%; - - .text { - flex: 1; - } - } - } - - .submenu { - - } - - &.show { - .menu-bt { - .icon-box { - .icon { - transform: rotate(360deg); + .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; } } } @@ -106,7 +122,7 @@ body { .separate { height: 0; - margin: 10px 0; + margin: 10px 5px; border: { width: 1px; color: constants.$font-secondary-color; diff --git a/src/components/common/HideScrollbar.tsx b/src/components/common/HideScrollbar.tsx index de3a10a..ab6d9cf 100644 --- a/src/components/common/HideScrollbar.tsx +++ b/src/components/common/HideScrollbar.tsx @@ -10,6 +10,7 @@ interface HideScrollbarProps isHiddenVerticalScrollbarWhenFull?: boolean isShowHorizontalScrollbar?: boolean isHiddenHorizontalScrollbarWhenFull?: boolean + minWidth?: string | number } export interface HideScrollbarElement { @@ -138,7 +139,6 @@ 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 [verticalScrollbarWidth, setVerticalScrollbarWidth] = useState(0) const [verticalScrollbarLength, setVerticalScrollbarLength] = useState(100) const [verticalScrollbarPosition, setVerticalScrollbarPosition] = useState(0) const [verticalScrollbarOnClick, setVerticalScrollbarOnClick] = useState(false) @@ -157,6 +157,7 @@ const HideScrollbar = forwardRef((prop isHiddenVerticalScrollbarWhenFull, isShowHorizontalScrollbar, isHiddenHorizontalScrollbarWhenFull, + minWidth, ..._props } = props @@ -365,9 +366,6 @@ const HideScrollbar = forwardRef((prop useEffect(() => { const windowResizeListener = () => { - setVerticalScrollbarWidth( - (rootRef.current?.offsetWidth ?? 0) - (rootRef.current?.clientWidth ?? 0) - ) setHorizontalScrollbarWidth( (rootRef.current?.offsetHeight ?? 0) - (rootRef.current?.clientHeight ?? 0) ) @@ -457,7 +455,6 @@ const HideScrollbar = forwardRef((prop className={'hide-scrollbar-selection'} tabIndex={0} style={{ - width: `calc(100vw + ${verticalScrollbarWidth}px)`, height: `calc(100vh + ${horizontalScrollbarWidth}px)`, touchAction: isPreventAnyScroll ? 'none' : '', msTouchAction: isPreventAnyScroll ? 'none' : '' @@ -469,7 +466,7 @@ const HideScrollbar = forwardRef((prop onTouchMove={isPreventAnyScroll ? handleDefaultTouchmove : undefined} onScroll={handleDefaultScroll} > -
      +
      {props.children}
      diff --git a/src/pages/HomeFramework.tsx b/src/pages/HomeFramework.tsx index d63978e..56993c1 100644 --- a/src/pages/HomeFramework.tsx +++ b/src/pages/HomeFramework.tsx @@ -81,6 +81,7 @@ const HomeFramework: React.FC = () => { ref={hideScrollbarRef} isPreventVerticalScroll={preventScroll} isShowHorizontalScrollbar={true} + minWidth={'900px'} >
      diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index a0dff56..941d44d 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -3,166 +3,129 @@ 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 _ from 'lodash' +import HideScrollbar from '@/components/common/HideScrollbar.tsx' const ToolsFramework: React.FC = () => { - const location = useLocation() - - const [multipleMenuShown, setMultipleMenuShown] = useState( - toolsJsonObjects.map((value) => ({ - id: value.id, - path: value.path, - shown: `${location.pathname}/`.startsWith(`/tools/${value.path}/`) - })) - ) - - useEffect(() => { - const temp = _.clone(multipleMenuShown) - temp.forEach((value) => { - value.shown = `${location.pathname}/`.startsWith(`/tools/${value.path}/`) - }) - setMultipleMenuShown(temp) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [location]) - - const switchSubmenu = (menuId: string) => { - return () => { - const temp = _.clone(multipleMenuShown) - const menu = temp.find(({ id }) => id === menuId) - menu && (menu.shown = !menu.shown) - setMultipleMenuShown(temp) - } - } - return ( <>
      -
      氮工具
      -
      -
        -
      • -
        -
      • -
      • -
        - - isPending ? 'pending' : isActive ? 'active' : '' - } - > - - {toolsJsonObjects[0].name} - -
        -
      • -
      • -
        - - isPending ? ' pending' : isActive ? ' active' : '' - } - > - - {toolsJsonObjects[1].name} - -
        -
      • -
      • -
        -
      • - {toolsJsonObjects.map((tool) => { - return tool.menu && - tool.id !== 'tools' && - tool.id !== 'tools-all' ? ( -
      • id === tool.id - )?.shown ?? false - ? ' show' - : '' - }` - : 'item' - } - key={tool.id} - > -
        - {tool.children ? ( -
        +
        氮工具
        +
        +
          +
        • +
          +
        • +
        • +
          + + isPending ? 'pending' : isActive ? 'active' : '' + } + > +
          + +
          + + {toolsJsonObjects[0].name} + +
          +
          +
        • +
        • +
          + + isPending ? ' pending' : isActive ? ' active' : '' + } + > +
          + +
          + + {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} - - isPending ? 'pending' : isActive ? 'active' : '' - } - > - {tool.children ? undefined : tool.icon ? ( - - ) : undefined} - {tool.name} - -
        - {tool.children ? ( -
          - {tool.children.map((subTool) => { - return subTool.menu ? ( -
        • - - isPending - ? 'pending' - : isActive - ? 'active' - : '' - } - > - {subTool.icon ? ( - - ) : undefined} - - {subTool.name} - - -
        • - ) : undefined - })} -
        - ) : undefined} -
      • - ) : undefined - })} -
      -
      + + ) : undefined + })} +
    +
  • +
diff --git a/src/router/tools.tsx b/src/router/tools.tsx index 5c675d2..e87043f 100644 --- a/src/router/tools.tsx +++ b/src/router/tools.tsx @@ -8,7 +8,6 @@ export const toolsJsonObjects: ToolsJsonObject[] = [ id: 'tools', component: React.lazy(() => import('@/pages/tools')), name: '主页', - icon: React.lazy(() => import('~icons/fatweb/logo.jsx')), menu: true, auth: false }, @@ -35,6 +34,7 @@ export const toolsJsonObjects: ToolsJsonObject[] = [ path: '1', id: '1', name: '翻译1', + icon: React.lazy(() => import('~icons/fatweb/logo.jsx')), menu: true, auth: false }, @@ -80,6 +80,186 @@ export const toolsJsonObjects: ToolsJsonObject[] = [ 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 } ] From 36b5acfea9425526a89d8cba12ca1c4f3a1af6a9 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Thu, 12 Oct 2023 23:26:10 +0800 Subject: [PATCH 022/307] Fix HideScrollbar --- src/components/common/HideScrollbar.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/common/HideScrollbar.tsx b/src/components/common/HideScrollbar.tsx index ab6d9cf..8ffa513 100644 --- a/src/components/common/HideScrollbar.tsx +++ b/src/components/common/HideScrollbar.tsx @@ -139,6 +139,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 [verticalScrollbarWidth, setVerticalScrollbarWidth] = useState(0) const [verticalScrollbarLength, setVerticalScrollbarLength] = useState(100) const [verticalScrollbarPosition, setVerticalScrollbarPosition] = useState(0) const [verticalScrollbarOnClick, setVerticalScrollbarOnClick] = useState(false) @@ -366,6 +367,9 @@ const HideScrollbar = forwardRef((prop useEffect(() => { const windowResizeListener = () => { + setVerticalScrollbarWidth( + (rootRef.current?.offsetWidth ?? 0) - (rootRef.current?.clientWidth ?? 0) + ) setHorizontalScrollbarWidth( (rootRef.current?.offsetHeight ?? 0) - (rootRef.current?.clientHeight ?? 0) ) @@ -455,6 +459,7 @@ const HideScrollbar = forwardRef((prop className={'hide-scrollbar-selection'} tabIndex={0} style={{ + width: `calc(100vw + ${verticalScrollbarWidth}px)`, height: `calc(100vh + ${horizontalScrollbarWidth}px)`, touchAction: isPreventAnyScroll ? 'none' : '', msTouchAction: isPreventAnyScroll ? 'none' : '' From 1c05de7f20b77ca49db01b44a8ba2baff1b39db4 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Thu, 12 Oct 2023 23:27:21 +0800 Subject: [PATCH 023/307] Temp --- .../css/components/common/hide-scrollbar.scss | 4 --- src/assets/css/pages/tools-framework.scss | 35 +++++++++++-------- src/pages/ToolsFramework.tsx | 10 +++--- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/assets/css/components/common/hide-scrollbar.scss b/src/assets/css/components/common/hide-scrollbar.scss index 5a9f7c9..df0b567 100644 --- a/src/assets/css/components/common/hide-scrollbar.scss +++ b/src/assets/css/components/common/hide-scrollbar.scss @@ -17,10 +17,6 @@ } } - ::-webkit-scrollbar { - display: none; - } - .scrollbar { position: absolute; z-index: 1000; diff --git a/src/assets/css/pages/tools-framework.scss b/src/assets/css/pages/tools-framework.scss index e9a54d0..9fbb602 100644 --- a/src/assets/css/pages/tools-framework.scss +++ b/src/assets/css/pages/tools-framework.scss @@ -6,10 +6,16 @@ body { } .left-panel { + display: flex; + flex-direction: column; width: 16%; background-color: constants.$origin-color; user-select: none; + .hide-scrollbar-selection { + height: 100% !important; + } + .title { font-size: 2em; text-align: center; @@ -61,8 +67,8 @@ body { } .submenu { - display: none; - position: fixed; + display: block; + position: absolute; padding-left: 20px; left: 100%; top: 0; @@ -99,7 +105,9 @@ body { } &:hover { - background-color: constants.$background-color; + a { + background-color: constants.$background-color; + } .submenu { display: block; @@ -119,20 +127,19 @@ body { } } } - - .separate { - height: 0; - margin: 10px 5px; - border: { - width: 1px; - color: constants.$font-secondary-color; - style: solid; - }; - opacity: 0.4; - } } } } + .separate { + height: 0; + margin: 10px 5px; + border: { + width: 1px; + color: constants.$font-secondary-color; + style: solid; + }; + opacity: 0.4; + } } .right-panel { diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index 941d44d..4cd80cf 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -10,13 +10,11 @@ const ToolsFramework: React.FC = () => { <>
- -
氮工具
+
氮工具
+
+
    -
  • -
    -
  • {
+ +
氮工具
From a8129e8afff86c3d36c5c211e97174a6a4b6743a Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Fri, 13 Oct 2023 11:14:24 +0800 Subject: [PATCH 024/307] Optimize HideScrollbar --- .../css/components/common/hide-scrollbar.scss | 7 +++++-- src/components/common/HideScrollbar.tsx | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/assets/css/components/common/hide-scrollbar.scss b/src/assets/css/components/common/hide-scrollbar.scss index df0b567..1d604dc 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; @@ -49,8 +50,9 @@ padding: 12px 4px; width: 16px; height: 100%; - right: 0; + left: 100%; top: 0; + transform: translateX(-100%); } .horizontal-scrollbar { @@ -58,6 +60,7 @@ width: 100%; height: 16px; left: 0; - bottom: 0; + top: 100%; + transform: translateY(-100%); } } \ No newline at end of file diff --git a/src/components/common/HideScrollbar.tsx b/src/components/common/HideScrollbar.tsx index 8ffa513..44e158f 100644 --- a/src/components/common/HideScrollbar.tsx +++ b/src/components/common/HideScrollbar.tsx @@ -459,8 +459,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' : '' }} @@ -482,6 +482,13 @@ const HideScrollbar = forwardRef((prop 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 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 + }} >
Date: Fri, 13 Oct 2023 11:33:14 +0800 Subject: [PATCH 025/307] Add scrollbar width setting to HideScrollbar --- .../css/components/common/hide-scrollbar.scss | 10 ++++++++-- src/components/common/HideScrollbar.tsx | 14 +++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/assets/css/components/common/hide-scrollbar.scss b/src/assets/css/components/common/hide-scrollbar.scss index 1d604dc..780168a 100644 --- a/src/assets/css/components/common/hide-scrollbar.scss +++ b/src/assets/css/components/common/hide-scrollbar.scss @@ -48,19 +48,25 @@ .vertical-scrollbar { padding: 12px 4px; - width: 16px; height: 100%; left: 100%; top: 0; transform: translateX(-100%); + + .box { + width: 8px; + } } .horizontal-scrollbar { padding: 4px 12px; width: 100%; - height: 16px; left: 0; top: 100%; transform: translateY(-100%); + + .box { + height: 8px; + } } } \ No newline at end of file diff --git a/src/components/common/HideScrollbar.tsx b/src/components/common/HideScrollbar.tsx index 44e158f..2cf3b8f 100644 --- a/src/components/common/HideScrollbar.tsx +++ b/src/components/common/HideScrollbar.tsx @@ -11,6 +11,8 @@ interface HideScrollbarProps isShowHorizontalScrollbar?: boolean isHiddenHorizontalScrollbarWhenFull?: boolean minWidth?: string | number + minHeight?: string | number + scrollbarWidth?: string | number } export interface HideScrollbarElement { @@ -159,6 +161,8 @@ const HideScrollbar = forwardRef((prop isShowHorizontalScrollbar, isHiddenHorizontalScrollbarWhenFull, minWidth, + minHeight, + scrollbarWidth, ..._props } = props @@ -471,7 +475,11 @@ const HideScrollbar = forwardRef((prop onTouchMove={isPreventAnyScroll ? handleDefaultTouchmove : undefined} onScroll={handleDefaultScroll} > -
+
{props.children}
@@ -490,7 +498,7 @@ const HideScrollbar = forwardRef((prop : undefined }} > -
+
((prop : undefined }} > -
+
Date: Fri, 13 Oct 2023 15:34:22 +0800 Subject: [PATCH 026/307] Optimize submenu in ToolsFramework --- .../css/components/common/hide-scrollbar.scss | 4 + src/assets/css/pages/tools-framework.scss | 35 ++- src/pages/ToolsFramework.tsx | 254 ++++++++++-------- src/router/tools.tsx | 18 +- 4 files changed, 187 insertions(+), 124 deletions(-) diff --git a/src/assets/css/components/common/hide-scrollbar.scss b/src/assets/css/components/common/hide-scrollbar.scss index 780168a..2cf545e 100644 --- a/src/assets/css/components/common/hide-scrollbar.scss +++ b/src/assets/css/components/common/hide-scrollbar.scss @@ -18,6 +18,10 @@ } } + ::-webkit-scrollbar { + display: none; + } + .scrollbar { position: absolute; z-index: 1000; diff --git a/src/assets/css/pages/tools-framework.scss b/src/assets/css/pages/tools-framework.scss index 9fbb602..d0ba705 100644 --- a/src/assets/css/pages/tools-framework.scss +++ b/src/assets/css/pages/tools-framework.scss @@ -12,10 +12,6 @@ body { background-color: constants.$origin-color; user-select: none; - .hide-scrollbar-selection { - height: 100% !important; - } - .title { font-size: 2em; text-align: center; @@ -26,9 +22,20 @@ body { } .content { - > ul { - > li { + display: flex; + min-height: 0; + flex-direction: column; + .toolsMenu { + min-height: 0; + + .hide-scrollbar-mask { + flex: 1; + } + } + + ul { + > li { &.item { position: relative; margin: 4px 14px; @@ -61,17 +68,16 @@ body { &.active { color: constants.$origin-color; - background-color: constants.$main-color; + background-color: constants.$main-color !important; } } } .submenu { - display: block; - position: absolute; + display: none; + position: fixed; padding-left: 20px; - left: 100%; - top: 0; + z-index: 10000; .content { display: flex; @@ -105,8 +111,10 @@ body { } &:hover { - a { - background-color: constants.$background-color; + .menu-bt { + a { + background-color: constants.$background-color; + } } .submenu { @@ -130,6 +138,7 @@ body { } } } + .separate { height: 0; margin: 10px 5px; diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index 4cd80cf..d69d129 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -6,125 +6,159 @@ import { toolsJsonObjects } from '@/router/tools.tsx' import HideScrollbar from '@/components/common/HideScrollbar.tsx' const ToolsFramework: React.FC = () => { + const [submenuTop, setSubmenuTop] = useState(0) + const [submenuLeft, setSubmenuLeft] = useState(0) + + 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].name} - -
    -
    -
  • -
  • -
    - - isPending ? ' pending' : isActive ? ' active' : '' - } - > -
    - -
    - - {toolsJsonObjects[1].name} - -
    -
    -
  • -
  • -
    -
  • - {toolsJsonObjects.map((tool) => { - return tool.menu && - tool.id !== 'tools' && - tool.id !== 'tools-all' ? ( -
  • -
    - - isPending - ? 'pending' - : isActive - ? 'active' - : '' - } +
    +
      +
    • +
      + + isPending ? 'pending' : isActive ? 'active' : '' + } + > +
      + +
      + {toolsJsonObjects[0].name} +
      +
      +
    • +
    • +
      + + isPending ? ' pending' : isActive ? ' active' : '' + } + > +
      + +
      + {toolsJsonObjects[1].name} +
      +
      +
    • +
    • +
      +
    • +
    +
    + +
      + {toolsJsonObjects.map((tool) => { + return tool.menu && + tool.id !== 'tools' && + tool.id !== 'tools-all' ? ( +
    • +
      -
      - {tool.icon ? ( - - ) : undefined} -
      - {tool.name} - -
      - {tool.children ? ( -
        -
        - {tool.children.map((subTool) => { - return subTool.menu ? ( -
      • - - isPending - ? 'pending' - : isActive - ? 'active' - : '' - } + + isPending + ? 'pending' + : isActive + ? 'active' + : '' + } + > +
        + {tool.icon ? ( + + ) : undefined} +
        + {tool.name} +
        +
      • + {tool.children ? ( +
          +
          + {tool.children.map((subTool) => { + return subTool.menu ? ( +
        • - - {subTool.name} - - -
        • - ) : undefined - })} -
          -
        - ) : undefined} - - ) : undefined - })} -
      + + isPending + ? 'pending' + : isActive + ? 'active' + : '' + } + > + + {subTool.name} + + +
    • + ) : undefined + })} +
    +
+ ) : undefined} + + ) : undefined + })} + +
-
- +
氮工具
diff --git a/src/router/tools.tsx b/src/router/tools.tsx index e87043f..b6c658b 100644 --- a/src/router/tools.tsx +++ b/src/router/tools.tsx @@ -259,7 +259,23 @@ export const toolsJsonObjects: ToolsJsonObject[] = [ name: '翻译--10-', icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')), menu: true, - auth: false + 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 + } + ] } ] From fea57c86327531e6e21d66a17e34ff0e345cc16e Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Fri, 13 Oct 2023 15:59:14 +0800 Subject: [PATCH 027/307] Optimize submenu in ToolsFramework --- src/assets/css/pages/tools-framework.scss | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/assets/css/pages/tools-framework.scss b/src/assets/css/pages/tools-framework.scss index d0ba705..3526d8e 100644 --- a/src/assets/css/pages/tools-framework.scss +++ b/src/assets/css/pages/tools-framework.scss @@ -8,7 +8,7 @@ body { .left-panel { display: flex; flex-direction: column; - width: 16%; + width: clamp(180px, 20vw, 240px); background-color: constants.$origin-color; user-select: none; @@ -25,13 +25,11 @@ body { display: flex; min-height: 0; flex-direction: column; + flex: 1; .toolsMenu { min-height: 0; - - .hide-scrollbar-mask { - flex: 1; - } + flex: 1; } ul { From c1691d3e8e2027f6ed731fce623311558986420f Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Fri, 13 Oct 2023 17:42:48 +0800 Subject: [PATCH 028/307] Optimize svg --- src/assets/svg/down.svg | 2 +- src/assets/svg/github.svg | 2 +- src/assets/svg/jenkins.svg | 2 +- src/assets/svg/loading.svg | 2 +- src/assets/svg/logo.svg | 2 +- src/assets/svg/menu.svg | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) 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/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/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 From a48af3da92902ff2bd2653e01fe2d1bba19ef54d Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Fri, 13 Oct 2023 17:51:38 +0800 Subject: [PATCH 029/307] Optimize HideScrollbar --- src/components/common/HideScrollbar.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/common/HideScrollbar.tsx b/src/components/common/HideScrollbar.tsx index 2cf3b8f..f86e5fb 100644 --- a/src/components/common/HideScrollbar.tsx +++ b/src/components/common/HideScrollbar.tsx @@ -45,6 +45,7 @@ export interface HideScrollbarElement { listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions ): void + refreshLayout(): void } const HideScrollbar = forwardRef((props, ref) => { @@ -128,6 +129,9 @@ const HideScrollbar = forwardRef((prop options?: boolean | EventListenerOptions ): void { rootRef.current?.removeEventListener(type, listener, options) + }, + refreshLayout(): void { + setRefreshTime(Date.now()) } } }, @@ -141,6 +145,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) @@ -479,6 +484,7 @@ const HideScrollbar = forwardRef((prop className={'hide-scrollbar-content'} ref={contentRef} style={{ minWidth, minHeight }} + data-refresh={refreshTime} > {props.children}
From e32f12d301e3250c053261676de0174d1c08fc15 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Fri, 13 Oct 2023 17:59:21 +0800 Subject: [PATCH 030/307] Finish sidebar in ToolsFramework --- src/assets/css/pages/tools-framework.scss | 74 +++++++++++++++++++---- src/assets/svg/expand.svg | 1 + src/pages/ToolsFramework.tsx | 42 +++++++++---- src/router/tools.tsx | 1 + 4 files changed, 95 insertions(+), 23 deletions(-) create mode 100644 src/assets/svg/expand.svg diff --git a/src/assets/css/pages/tools-framework.scss b/src/assets/css/pages/tools-framework.scss index 3526d8e..46c8d95 100644 --- a/src/assets/css/pages/tools-framework.scss +++ b/src/assets/css/pages/tools-framework.scss @@ -11,14 +11,41 @@ body { width: clamp(180px, 20vw, 240px); background-color: constants.$origin-color; user-select: none; + transition: all .3s; + white-space: nowrap; .title { - font-size: 2em; - text-align: center; + display: flex; + align-items: center; font-weight: bold; - letter-spacing: 0.6em; - padding: 10px; + 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; + 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; + } } .content { @@ -42,26 +69,29 @@ body { .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; - width: 26px; - height: 26px; - - .icon { - margin-right: 16px; - } } a { display: flex; - padding: 8px 16px; + align-items: center; height: 100%; width: 100%; transition: all 0.2s; .text { flex: 1; + padding-left: 8px; } &.active { @@ -147,6 +177,28 @@ body { }; opacity: 0.4; } + + &.hide { + width: 68px; + + .title { + .icon-box { + span { + transform: rotateZ(360deg); + transition: all .3s; + } + } + .text { + display: none; + } + } + + .menu-bt { + .text { + display: none; + } + } + } } .right-panel { 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/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index d69d129..f9e94bb 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -3,11 +3,20 @@ 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 from '@/components/common/HideScrollbar.tsx' +import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar.tsx' const ToolsFramework: React.FC = () => { + const hideScrollbarRef = useRef(null) const [submenuTop, setSubmenuTop] = useState(0) const [submenuLeft, setSubmenuLeft] = useState(0) + const [hideSidebar, setHideSidebar] = useState(false) + + const switchSidebar = () => { + setHideSidebar(!hideSidebar) + setTimeout(() => { + hideScrollbarRef.current?.refreshLayout() + }, 300) + } const showSubmenu = (e: React.MouseEvent) => { const parentElement = e.currentTarget.parentElement @@ -29,8 +38,13 @@ const ToolsFramework: React.FC = () => { return ( <> -
-
氮工具
+
+
+ + + + 氮工具 +
    @@ -44,10 +58,12 @@ const ToolsFramework: React.FC = () => { } >
    - + {toolsJsonObjects[0].icon ? ( + + ) : undefined}
    {toolsJsonObjects[0].name} @@ -62,10 +78,12 @@ const ToolsFramework: React.FC = () => { } >
    - + {toolsJsonObjects[1].icon ? ( + + ) : undefined}
    {toolsJsonObjects[1].name} @@ -78,8 +96,8 @@ const ToolsFramework: React.FC = () => {
      {toolsJsonObjects.map((tool) => { diff --git a/src/router/tools.tsx b/src/router/tools.tsx index b6c658b..a98995c 100644 --- a/src/router/tools.tsx +++ b/src/router/tools.tsx @@ -7,6 +7,7 @@ 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 From 05c68a2ab78ff575a2ed80f7c2b9b430a7700210 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Fri, 13 Oct 2023 18:18:08 +0800 Subject: [PATCH 031/307] Add sidebar footer in ToolsFramework.tsx --- src/assets/css/pages/tools-framework.scss | 36 +++++++++++++++++++++++ src/pages/ToolsFramework.tsx | 14 +++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/assets/css/pages/tools-framework.scss b/src/assets/css/pages/tools-framework.scss index 46c8d95..f1dc864 100644 --- a/src/assets/css/pages/tools-framework.scss +++ b/src/assets/css/pages/tools-framework.scss @@ -30,6 +30,7 @@ body { height: 40px; font-size: constants.$SIZE_ICON_SM; border-radius: 8px; + cursor: pointer; span { transform: rotateZ(180deg); transition: all .3s; @@ -45,6 +46,7 @@ body { font-size: 2em; text-align: center; letter-spacing: 0.6em; + transform: translateX(0.3em); } } @@ -178,6 +180,40 @@ body { opacity: 0.4; } + .footer { + 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; + 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; + } + } + &.hide { width: 68px; diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index f9e94bb..519ea17 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -4,15 +4,17 @@ 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(false) + const [hideSidebar, setHideSidebar] = useState(getLocalStorage('hideSidebar') === 'false') const switchSidebar = () => { setHideSidebar(!hideSidebar) + setLocalStorage('hideSidebar', hideSidebar ? 'true' : 'false') setTimeout(() => { hideScrollbarRef.current?.refreshLayout() }, 300) @@ -90,7 +92,7 @@ const ToolsFramework: React.FC = () => {
  • -
    +
@@ -177,7 +179,13 @@ const ToolsFramework: React.FC = () => {
-
氮工具
+
+
+ + + + 氮工具 +
From fdc2135ce24978510321ca1000e260b23cbda31c Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Fri, 13 Oct 2023 22:12:09 +0800 Subject: [PATCH 032/307] Optimize HideScrollbar --- src/components/common/HideScrollbar.tsx | 44 +++++++++++++++---------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/components/common/HideScrollbar.tsx b/src/components/common/HideScrollbar.tsx index f86e5fb..2af301c 100644 --- a/src/components/common/HideScrollbar.tsx +++ b/src/components/common/HideScrollbar.tsx @@ -13,6 +13,7 @@ interface HideScrollbarProps minWidth?: string | number minHeight?: string | number scrollbarWidth?: string | number + animationTransitionTime?: number } export interface HideScrollbarElement { @@ -131,7 +132,7 @@ const HideScrollbar = forwardRef((prop rootRef.current?.removeEventListener(type, listener, options) }, refreshLayout(): void { - setRefreshTime(Date.now()) + refreshLayout() } } }, @@ -168,6 +169,7 @@ const HideScrollbar = forwardRef((prop minWidth, minHeight, scrollbarWidth, + animationTransitionTime, ..._props } = props @@ -374,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 } @@ -493,7 +503,7 @@ const HideScrollbar = forwardRef((prop hidden={ !isShowVerticalScrollbar || ((isHiddenVerticalScrollbarWhenFull ?? true) && - verticalScrollbarLength === 100) + verticalScrollbarLength >= 100) } className={'scrollbar vertical-scrollbar'} style={{ @@ -530,7 +540,7 @@ const HideScrollbar = forwardRef((prop hidden={ !isShowHorizontalScrollbar || ((isHiddenHorizontalScrollbarWhenFull ?? true) && - horizontalScrollbarLength === 100) + horizontalScrollbarLength >= 100) } className={'scrollbar horizontal-scrollbar'} style={{ From 507ed1eb9a1fd615526a03be1b7010f07cbf3c42 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Sat, 14 Oct 2023 01:09:02 +0800 Subject: [PATCH 033/307] Finish sidebar in ToolsFramework --- src/assets/css/pages/tools-framework.scss | 36 +++++++++++++---------- src/assets/svg/home.svg | 1 + src/assets/svg/user.svg | 1 + src/components/common/HideScrollbar.tsx | 1 + src/pages/ToolsFramework.tsx | 7 +++-- 5 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 src/assets/svg/home.svg create mode 100644 src/assets/svg/user.svg diff --git a/src/assets/css/pages/tools-framework.scss b/src/assets/css/pages/tools-framework.scss index f1dc864..b95691f 100644 --- a/src/assets/css/pages/tools-framework.scss +++ b/src/assets/css/pages/tools-framework.scss @@ -13,6 +13,7 @@ body { user-select: none; transition: all .3s; white-space: nowrap; + overflow: hidden; .title { display: flex; @@ -184,33 +185,30 @@ body { display: flex; align-items: center; font-weight: bold; - padding: 10px 14px; + padding: 8px 14px; color: constants.$main-color; .icon-box { display: flex; justify-content: center; align-items: center; + margin-left: 4px; padding: 10px; - width: 40px; - height: 40px; - font-size: constants.$SIZE_ICON_SM; - border-radius: 8px; - span { - transform: rotateZ(180deg); - transition: all .3s; - } - - &:hover { - background-color: constants.$background-color; - } + 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; - font-size: 2em; - text-align: center; - letter-spacing: 0.6em; + padding-left: 8px; + font-size: 1.4em; + color: constants.$font-main-color; + user-select: text; } } @@ -234,6 +232,12 @@ body { display: none; } } + + .footer { + .text { + display: none; + } + } } } 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/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/HideScrollbar.tsx b/src/components/common/HideScrollbar.tsx index 2af301c..df8f916 100644 --- a/src/components/common/HideScrollbar.tsx +++ b/src/components/common/HideScrollbar.tsx @@ -446,6 +446,7 @@ const HideScrollbar = forwardRef((prop window.removeEventListener('resize', windowResizeListener) } }, [ + animationTransitionTime, horizontalScrollbarLength, isPreventAnyScroll, isPreventHorizontalScroll, diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index 519ea17..fb3c76d 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -99,6 +99,7 @@ const ToolsFramework: React.FC = () => {
    @@ -181,10 +182,10 @@ const ToolsFramework: React.FC = () => {
- - + + - 氮工具 + 未登录
From 87ca67ba3c9b855b14422f36f96440e4c89f2fd1 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Sat, 14 Oct 2023 18:51:19 +0800 Subject: [PATCH 034/307] Change the icon of home and all tools in ToolsFramework --- src/assets/svg/tool.svg | 1 + src/router/tools.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/assets/svg/tool.svg diff --git a/src/assets/svg/tool.svg b/src/assets/svg/tool.svg new file mode 100644 index 0000000..0a3973c --- /dev/null +++ b/src/assets/svg/tool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/tools.tsx b/src/router/tools.tsx index a98995c..a3a4fcd 100644 --- a/src/router/tools.tsx +++ b/src/router/tools.tsx @@ -7,7 +7,7 @@ export const toolsJsonObjects: ToolsJsonObject[] = [ path: '', id: 'tools', component: React.lazy(() => import('@/pages/tools')), - icon: React.lazy(() => import('~icons/fatweb/logo.jsx')), + icon: React.lazy(() => import('~icons/fatweb/home.jsx')), name: '主页', menu: true, auth: false @@ -18,7 +18,7 @@ export const toolsJsonObjects: ToolsJsonObject[] = [ component: React.lazy(() => import('@/pages/tools')), name: '全部工具', titlePostfix: ' - 全部工具', - icon: React.lazy(() => import('~icons/fatweb/logo.jsx')), + icon: React.lazy(() => import('~icons/fatweb/tool.jsx')), menu: true, auth: false }, From de3e34b08549e6f1cc24dceada962ca3e5725855 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Sat, 14 Oct 2023 19:53:28 +0800 Subject: [PATCH 035/307] Add auto hide scrollbar to HideScrollbar --- .../css/components/common/hide-scrollbar.scss | 17 +++++++++ src/components/common/HideScrollbar.tsx | 38 ++++++++++++++++++- src/pages/ToolsFramework.tsx | 1 + 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/assets/css/components/common/hide-scrollbar.scss b/src/assets/css/components/common/hide-scrollbar.scss index 2cf545e..32076ac 100644 --- a/src/assets/css/components/common/hide-scrollbar.scss +++ b/src/assets/css/components/common/hide-scrollbar.scss @@ -1,4 +1,5 @@ @use '@/assets/css/constants' as constants; +@use '@/assets/css/mixins' as mixins; .hide-scrollbar-mask { position: relative; @@ -44,10 +45,26 @@ background-color: constants.$font-secondary-color; transition: background-color .2s; } + :hover { background-color: constants.$font-main-color; } } + + &.hide { + display: block; + opacity: 0; + animation: 0.4s linear; + + @include mixins.unique-keyframes { + 0% { + opacity: 0.5; + } + 100% { + opacity: 0; + } + } + } } .vertical-scrollbar { diff --git a/src/components/common/HideScrollbar.tsx b/src/components/common/HideScrollbar.tsx index df8f916..944c772 100644 --- a/src/components/common/HideScrollbar.tsx +++ b/src/components/common/HideScrollbar.tsx @@ -14,6 +14,7 @@ interface HideScrollbarProps minHeight?: string | number scrollbarWidth?: string | number animationTransitionTime?: number + autoHideWaitingTime?: number } export interface HideScrollbarElement { @@ -147,16 +148,20 @@ const HideScrollbar = forwardRef((prop 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) const [verticalScrollbarOnClick, setVerticalScrollbarOnClick] = useState(false) const [verticalScrollbarOnTouch, setVerticalScrollbarOnTouch] = useState(false) + const [verticalScrollbarAutoHide, setVerticalScrollbarAutoHide] = useState(false) + const [horizontalScrollbarWidth, setHorizontalScrollbarWidth] = useState(0) const [horizontalScrollbarLength, setHorizontalScrollbarLength] = useState(100) const [horizontalScrollbarPosition, setHorizontalScrollbarPosition] = useState(0) const [horizontalScrollbarOnClick, setHorizontalScrollbarOnClick] = useState(false) const [horizontalScrollbarOnTouch, setHorizontalScrollbarOnTouch] = useState(false) + const [horizontalScrollbarAutoHide, setHorizontalScrollbarAutoHide] = useState(false) const { isPreventScroll, @@ -170,12 +175,37 @@ const HideScrollbar = forwardRef((prop minHeight, scrollbarWidth, animationTransitionTime, + autoHideWaitingTime, ..._props } = props const isPreventAnyScroll = isPreventScroll || isPreventVerticalScroll || isPreventHorizontalScroll + useEffect(() => { + if (autoHideWaitingTime === undefined) { + return + } + setVerticalScrollbarAutoHide(false) + if (autoHideWaitingTime > 0) { + setTimeout(() => { + setVerticalScrollbarAutoHide(true) + }, autoHideWaitingTime) + } + }, [autoHideWaitingTime, verticalScrollbarPosition]) + + useEffect(() => { + if (autoHideWaitingTime === undefined) { + return + } + setHorizontalScrollbarAutoHide(false) + if (autoHideWaitingTime > 0) { + setTimeout(() => { + setHorizontalScrollbarAutoHide(true) + }, autoHideWaitingTime) + } + }, [autoHideWaitingTime, horizontalScrollbarPosition]) + const handleDefaultTouchStart = useCallback( (event: React.TouchEvent) => { if (event.touches.length !== 1 || isPreventScroll) { @@ -506,7 +536,9 @@ const HideScrollbar = forwardRef((prop ((isHiddenVerticalScrollbarWhenFull ?? true) && verticalScrollbarLength >= 100) } - className={'scrollbar vertical-scrollbar'} + className={`scrollbar vertical-scrollbar${ + verticalScrollbarAutoHide ? ' hide' : '' + }`} style={{ height: maskRef.current ? maskRef.current?.clientHeight - 1 : undefined, top: maskRef.current?.clientTop, @@ -543,7 +575,9 @@ const HideScrollbar = forwardRef((prop ((isHiddenHorizontalScrollbarWhenFull ?? true) && horizontalScrollbarLength >= 100) } - className={'scrollbar horizontal-scrollbar'} + className={`scrollbar horizontal-scrollbar${ + horizontalScrollbarAutoHide ? ' hide' : '' + }`} style={{ width: maskRef.current ? maskRef.current?.clientWidth - 1 : undefined, left: maskRef.current?.clientLeft, diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index fb3c76d..c29b28f 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -100,6 +100,7 @@ const ToolsFramework: React.FC = () => { isShowVerticalScrollbar={true} scrollbarWidth={2} animationTransitionTime={300} + autoHideWaitingTime={800} ref={hideScrollbarRef} >
    From 8b5880fab5de8c05cd4fa8d755039a04b6c0cc8d Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Sat, 14 Oct 2023 23:00:52 +0800 Subject: [PATCH 036/307] Add login to ToolsFramework --- src/AuthRoute.tsx | 5 +- src/assets/css/constants.scss | 3 ++ src/assets/css/pages/tools-framework.scss | 66 +++++++++++++++++++++-- src/assets/svg/exit.svg | 1 + src/pages/ToolsFramework.tsx | 58 ++++++++++++++++++-- src/router/tools.tsx | 5 ++ src/services/index.tsx | 4 +- src/utils/auth.ts | 7 +-- src/utils/common.ts | 4 ++ 9 files changed, 136 insertions(+), 17 deletions(-) create mode 100644 src/assets/svg/exit.svg diff --git a/src/AuthRoute.tsx b/src/AuthRoute.tsx index 7335e5e..35b6a77 100644 --- a/src/AuthRoute.tsx +++ b/src/AuthRoute.tsx @@ -1,5 +1,6 @@ import { getLoginStatus } from '@/utils/auth.ts' import { PRODUCTION_NAME } from '@/constants/Common.constants.ts' +import { getRedirectUrl } from '@/utils/common.ts' const AuthRoute = () => { const matches = useMatches() @@ -16,9 +17,7 @@ const AuthRoute = () => { if (matches.some(({ handle }) => (handle as RouteHandle)?.auth) && !isLogin) { return ( ) } diff --git a/src/assets/css/constants.scss b/src/assets/css/constants.scss index 18e8338..3e6e1b5 100644 --- a/src/assets/css/constants.scss +++ b/src/assets/css/constants.scss @@ -1,6 +1,9 @@ $origin-color: white; $main-color: #4E47BB; $secondary-color: #BAB8E5; +$error-color: #ff4d4f; +$error-secondary-color: #ff7875; +$blue-color: #1677ff; $active-color: #EBECFFD; $background-color: #F5F5F5; $font-main-color: #4D4D4D; diff --git a/src/assets/css/pages/tools-framework.scss b/src/assets/css/pages/tools-framework.scss index b95691f..4ae1958 100644 --- a/src/assets/css/pages/tools-framework.scss +++ b/src/assets/css/pages/tools-framework.scss @@ -13,7 +13,6 @@ body { user-select: none; transition: all .3s; white-space: nowrap; - overflow: hidden; .title { display: flex; @@ -21,6 +20,7 @@ body { font-weight: bold; padding: 10px 14px; color: constants.$main-color; + overflow: hidden; .icon-box { display: flex; @@ -153,12 +153,10 @@ body { animation: 0.3s ease; @include mixins.unique-keyframes { 0% { - display: block; transform: translateX(-10px); opacity: 0; } 100% { - display: block; transform: translateX(0); opacity: 1; } @@ -188,7 +186,7 @@ body { padding: 8px 14px; color: constants.$main-color; - .icon-box { + .icon-user { display: flex; justify-content: center; align-items: center; @@ -205,10 +203,27 @@ body { .text { flex: 1; - padding-left: 8px; + padding-left: 10px; font-size: 1.4em; color: constants.$font-main-color; user-select: text; + + a{ + color: constants.$main-color; + text-decoration: underline; + } + } + + .icon-exit { + font-size: constants.$SIZE_ICON_XS; + color: constants.$error-color; + padding: 6px 10px; + cursor: pointer; + + &:hover { + border-radius: 8px; + background-color: constants.$background-color; + } } } @@ -234,9 +249,50 @@ body { } .footer { + position: relative; .text { display: none; } + + .submenu-exit { + display: none; + position: absolute; + padding-left: 6px; + left: 100%; + + .content { + padding: 8px; + border-radius: 8px; + background-color: constants.$origin-color; + + .icon-exit { + padding: 4px 8px; + &:hover { + border-radius: 8px; + background-color: constants.$background-color; + } + } + } + + &.hide { + display: none!important; + } + } + + &:hover .submenu-exit { + display: block; + animation: 0.3s ease; + @include mixins.unique-keyframes { + 0% { + transform: translateX(-10px); + opacity: 0; + } + 100% { + transform: translateX(0); + opacity: 1; + } + } + } } } } diff --git a/src/assets/svg/exit.svg b/src/assets/svg/exit.svg new file mode 100644 index 0000000..12a1804 --- /dev/null +++ b/src/assets/svg/exit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index c29b28f..20f62a9 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -4,9 +4,15 @@ 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' +import { getLocalStorage, getRedirectUrl, setLocalStorage } from '@/utils/common.ts' +import { getLoginStatus, logout } from '@/utils/auth.ts' +import { NavLink, Outlet } from 'react-router-dom' const ToolsFramework: React.FC = () => { + const matches = useMatches() + const lastMatch = matches.reduce((_, second) => second) + const location = useLocation() + const navigate = useNavigate() const hideScrollbarRef = useRef(null) const [submenuTop, setSubmenuTop] = useState(0) const [submenuLeft, setSubmenuLeft] = useState(0) @@ -37,6 +43,21 @@ const ToolsFramework: React.FC = () => { } } + const handleClickAvatar = () => { + if (getLoginStatus()) { + navigate(`${lastMatch.pathname}${location.search}`) + } else { + navigate(getRedirectUrl('/login', `${lastMatch.pathname}${location.search}`)) + } + } + + const handleLogout = () => { + logout() + setTimeout(() => { + window.location.reload() + }, 1500) + } + return ( <> @@ -183,13 +204,42 @@ const ToolsFramework: React.FC = () => {
- + - 未登录 + + +
-
+
+ +
) diff --git a/src/router/tools.tsx b/src/router/tools.tsx index a3a4fcd..997809a 100644 --- a/src/router/tools.tsx +++ b/src/router/tools.tsx @@ -309,4 +309,9 @@ const tools: RouteObject[] = toolsJsonObjects.map((value) => ({ })) })) +tools.push({ + path: '*', + element: +}) + export default tools diff --git a/src/services/index.tsx b/src/services/index.tsx index d89ba76..2117765 100644 --- a/src/services/index.tsx +++ b/src/services/index.tsx @@ -42,7 +42,7 @@ service.interceptors.request.use( ) { await axios .get(import.meta.env.VITE_API_TOKEN_URL, { - headers: { token } + headers: { Authorization: `Bearer ${token}` } }) .then((value: AxiosResponse<_Response>) => { const response = value.data @@ -53,7 +53,7 @@ service.interceptors.request.use( } token = getToken() - config.headers.set('token', token) + config.headers.set('Authorization', `Bearer ${token}`) } return config }, diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 097e112..22a7817 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,4 +1,4 @@ -import { clearLocalStorage, getCaptcha, getLocalStorage, setLocalStorage } from './common' +import { getCaptcha, getLocalStorage, removeLocalStorage, setLocalStorage } from './common' import { SYSTEM_OK, TOKEN_NAME } from '@/constants/Common.constants' import request from '@/services' @@ -12,8 +12,9 @@ export async function login(username: string, password: string) { } export function logout(): void { - void request.get('/logout').finally(() => { - clearLocalStorage() + void request.post('/logout').finally(() => { + removeLocalStorage('userInfo') + removeLocalStorage(TOKEN_NAME) }) } diff --git a/src/utils/common.ts b/src/utils/common.ts index 7d0f9ce..18bc410 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -134,3 +134,7 @@ function randomFloat(start: number, end: number): number { function randomColor(start: number, end: number): string { return `rgb(${randomInt(start, end)},${randomInt(start, end)},${randomInt(start, end)})` } + +export const getRedirectUrl = (path: string, redirectUrl: string): string => { + return `${path}?redirect=${encodeURIComponent(redirectUrl)}` +} From 7f709ac34158c50f304f12005937e2a6f47a6c90 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Sat, 14 Oct 2023 23:20:25 +0800 Subject: [PATCH 037/307] Recode old code --- build/resolvers/antd.ts | 4 ++-- src/services/index.tsx | 2 +- src/utils/auth.ts | 16 ++++++++-------- src/utils/common.ts | 32 ++++++++++++++++---------------- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/build/resolvers/antd.ts b/build/resolvers/antd.ts index 469af1b..3d18000 100644 --- a/build/resolvers/antd.ts +++ b/build/resolvers/antd.ts @@ -1,4 +1,4 @@ -export function kebabCase(key: string): string { +export const kebabCase = (key: string) => { const result: string = key.replace(/([A-Z])/g, ' $1').trim() return result.split(' ').join('-').toLowerCase() } @@ -395,7 +395,7 @@ const isAntd = (compName: string): boolean => { return antdNames.has(compName) } -export function AntDesignResolver(options: AntDesignResolverOptions = {}): ComponentResolver { +export const AntDesignResolver = (options: AntDesignResolverOptions = {}): ComponentResolver => { return { type: 'component', resolve: (name: string) => { diff --git a/src/services/index.tsx b/src/services/index.tsx index 2117765..3d3a308 100644 --- a/src/services/index.tsx +++ b/src/services/index.tsx @@ -74,7 +74,7 @@ service.interceptors.response.use( 登录已过期 ) - setTimeout(function () { + setTimeout(() => { location.reload() }, 1500) throw response?.data diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 22a7817..a923968 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -4,25 +4,25 @@ import request from '@/services' let captcha: Captcha -export async function login(username: string, password: string) { +export const login = async (username: string, password: string) => { return await request.post('/login', { username, password }) } -export function logout(): void { +export const logout = () => { void request.post('/logout').finally(() => { removeLocalStorage('userInfo') removeLocalStorage(TOKEN_NAME) }) } -export function getLoginStatus(): boolean { +export const getLoginStatus = () => { return getLocalStorage(TOKEN_NAME) !== null } -export async function getUser(): Promise { +export const getUser = async (): Promise => { if (getLocalStorage('userInfo') !== null) { return new Promise((resolve) => { resolve(JSON.parse(getLocalStorage('userInfo') as string) as User) @@ -31,7 +31,7 @@ export async function getUser(): Promise { return requestUser() } -export async function requestUser(): Promise { +export const requestUser = async () => { let user: User | null await request.get('/user/info').then((value) => { @@ -50,17 +50,17 @@ export async function requestUser(): Promise { }) } -export async function getUsername(): Promise { +export const getUsername = async () => { const user = await getUser() return user.username } -export function getCaptchaSrc(): string { +export const getCaptchaSrc = () => { captcha = getCaptcha(300, 150, 4) return captcha.base64Src } -export function verifyCaptcha(value: string): boolean { +export const verifyCaptcha = (value: string) => { return captcha.value.toLowerCase() === value.replace(/\s*/g, '').toLowerCase() } diff --git a/src/utils/common.ts b/src/utils/common.ts index 18bc410..04d1244 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,6 +1,6 @@ import { TOKEN_NAME } from '@/constants/Common.constants' -export function getQueryVariable(variable: string): string | null { +export const getQueryVariable = (variable: string) => { const query = window.location.search.substring(1) const vars = query.split('&') for (const value of vars) { @@ -12,12 +12,12 @@ export function getQueryVariable(variable: string): string | null { return null } -export function setCookie( +export const setCookie = ( name: string, value: string, daysToLive: number | null, path: string | null -): void { +) => { let cookie = `${name}=${encodeURIComponent(value)}` if (typeof daysToLive === 'number') { @@ -31,15 +31,15 @@ export function setCookie( document.cookie = cookie } -export function setLocalStorage(name: string, value: string): void { +export const setLocalStorage = (name: string, value: string) => { localStorage.setItem(name, value) } -export function setToken(token: string): void { +export const setToken = (token: string) => { setLocalStorage(TOKEN_NAME, token) } -export function getCookie(name: string): string | null { +export const getCookie = (name: string) => { const cookieArr = document.cookie.split(';') for (const cookie of cookieArr) { @@ -52,31 +52,31 @@ export function getCookie(name: string): string | null { return null } -export function getLocalStorage(name: string): string | null { +export const getLocalStorage = (name: string) => { return localStorage.getItem(name) } -export function getToken(): string | null { +export const getToken = () => { return getLocalStorage(TOKEN_NAME) } -export function removeCookie(name: string): void { +export const removeCookie = (name: string) => { document.cookie = `${name}=; max-age=0` } -export function removeLocalStorage(name: string): void { +export const removeLocalStorage = (name: string) => { localStorage.removeItem(name) } -export function removeToken(): void { +export const removeToken = () => { removeLocalStorage(TOKEN_NAME) } -export function clearLocalStorage(): void { +export const clearLocalStorage = () => { localStorage.clear() } -export function getCaptcha(width: number, high: number, num: number): Captcha { +export const getCaptcha = (width: number, high: number, num: number) => { const CHARTS = '23456789ABCDEFGHJKLMNPRSTUVWXYZabcdefghijklmnpqrstuvwxyz'.split('') const canvas = document.createElement('canvas') @@ -116,7 +116,7 @@ export function getCaptcha(width: number, high: number, num: number): Captcha { } } -function randomInt(start: number, end: number): number { +const randomInt = (start: number, end: number) => { if (start > end) { const t = start start = end @@ -127,11 +127,11 @@ function randomInt(start: number, end: number): number { return start + Math.floor(Math.random() * (end - start)) } -function randomFloat(start: number, end: number): number { +const randomFloat = (start: number, end: number) => { return start + Math.random() * (end - start) } -function randomColor(start: number, end: number): string { +const randomColor = (start: number, end: number) => { return `rgb(${randomInt(start, end)},${randomInt(start, end)},${randomInt(start, end)})` } From 6e31eb6e9d10f42adb039d2d943d366d659e3d12 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Sat, 14 Oct 2023 23:20:48 +0800 Subject: [PATCH 038/307] Optimize stylesheet in ToolsFramework --- src/assets/css/pages/tools-framework.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/css/pages/tools-framework.scss b/src/assets/css/pages/tools-framework.scss index 4ae1958..f9e08cf 100644 --- a/src/assets/css/pages/tools-framework.scss +++ b/src/assets/css/pages/tools-framework.scss @@ -192,8 +192,8 @@ body { align-items: center; margin-left: 4px; padding: 10px; - width: 32px; - height: 32px; + width: 36px; + height: 36px; font-size: constants.$SIZE_ICON_XS; border: 2px constants.$font-secondary-color solid; color: constants.$font-secondary-color; From aa07361813f91b35469cce809a016d07c91299b9 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Sun, 15 Oct 2023 12:51:59 +0800 Subject: [PATCH 039/307] Fix logout bug --- src/pages/ToolsFramework.tsx | 9 +++++---- src/utils/auth.ts | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index 20f62a9..c386c86 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -52,10 +52,11 @@ const ToolsFramework: React.FC = () => { } const handleLogout = () => { - logout() - setTimeout(() => { - window.location.reload() - }, 1500) + void logout().finally(() => { + setTimeout(() => { + window.location.reload() + }, 1500) + }) } return ( diff --git a/src/utils/auth.ts b/src/utils/auth.ts index a923968..aa3fc82 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -11,8 +11,8 @@ export const login = async (username: string, password: string) => { }) } -export const logout = () => { - void request.post('/logout').finally(() => { +export const logout = async () => { + return request.post('/logout').finally(() => { removeLocalStorage('userInfo') removeLocalStorage(TOKEN_NAME) }) From 2d1eb8d9e231515ead26f75c94d996c8c6bd7eb5 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Sun, 15 Oct 2023 16:46:35 +0800 Subject: [PATCH 040/307] Optimize remove token --- src/services/index.tsx | 14 ++++++++++++-- src/utils/auth.ts | 5 ++--- src/utils/common.ts | 1 + 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/services/index.tsx b/src/services/index.tsx index 3d3a308..8aca097 100644 --- a/src/services/index.tsx +++ b/src/services/index.tsx @@ -1,6 +1,6 @@ import axios, { type AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' import jwtDecode, { JwtPayload } from 'jwt-decode' -import { clearLocalStorage, getToken, setToken } from '@/utils/common' +import { getToken, removeToken, setToken } from '@/utils/common' import { SYSTEM_ACCESS_DENIED, SYSTEM_TOKEN_HAS_EXPIRED, @@ -66,9 +66,19 @@ service.interceptors.response.use( (response: AxiosResponse<_Response>) => { switch (response.data.code) { case SYSTEM_UNAUTHORIZED: + removeToken() + void message.error( + <> + 未登录 + + ) + setTimeout(() => { + location.reload() + }, 1500) + throw response?.data case SYSTEM_TOKEN_ILLEGAL: case SYSTEM_TOKEN_HAS_EXPIRED: - clearLocalStorage() + removeToken() void message.error( <> 登录已过期 diff --git a/src/utils/auth.ts b/src/utils/auth.ts index aa3fc82..c2f3155 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,4 +1,4 @@ -import { getCaptcha, getLocalStorage, removeLocalStorage, setLocalStorage } from './common' +import { getCaptcha, getLocalStorage, removeToken, setLocalStorage } from './common' import { SYSTEM_OK, TOKEN_NAME } from '@/constants/Common.constants' import request from '@/services' @@ -13,8 +13,7 @@ export const login = async (username: string, password: string) => { export const logout = async () => { return request.post('/logout').finally(() => { - removeLocalStorage('userInfo') - removeLocalStorage(TOKEN_NAME) + removeToken() }) } diff --git a/src/utils/common.ts b/src/utils/common.ts index 04d1244..c74b23e 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -69,6 +69,7 @@ export const removeLocalStorage = (name: string) => { } export const removeToken = () => { + removeLocalStorage('userInfo') removeLocalStorage(TOKEN_NAME) } From 2af3219e593cd150fe14c4762de6de577c2d9082 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Sun, 15 Oct 2023 18:16:30 +0800 Subject: [PATCH 041/307] Optimize code --- src/AuthRoute.tsx | 6 +-- src/components/common/LoadingMask.tsx | 2 +- src/constants/Common.constants.ts | 67 --------------------------- src/constants/common.constants.ts | 35 ++++++++++++++ src/constants/urls.constants.ts | 3 ++ src/pages/HomeFramework.tsx | 2 +- src/pages/Login.tsx | 6 +-- src/pages/ToolsFramework.tsx | 8 ++-- src/services/index.tsx | 2 +- src/utils/auth.ts | 15 +++--- src/utils/common.ts | 10 ++-- 11 files changed, 64 insertions(+), 92 deletions(-) delete mode 100644 src/constants/Common.constants.ts create mode 100644 src/constants/common.constants.ts create mode 100644 src/constants/urls.constants.ts diff --git a/src/AuthRoute.tsx b/src/AuthRoute.tsx index 35b6a77..c3e3cda 100644 --- a/src/AuthRoute.tsx +++ b/src/AuthRoute.tsx @@ -1,6 +1,6 @@ -import { getLoginStatus } from '@/utils/auth.ts' -import { PRODUCTION_NAME } from '@/constants/Common.constants.ts' -import { getRedirectUrl } from '@/utils/common.ts' +import { getLoginStatus } from '@/utils/auth' +import { PRODUCTION_NAME } from '@/constants/common.constants' +import { getRedirectUrl } from '@/utils/common' const AuthRoute = () => { const matches = useMatches() diff --git a/src/components/common/LoadingMask.tsx b/src/components/common/LoadingMask.tsx index 84c09e8..86d2548 100644 --- a/src/components/common/LoadingMask.tsx +++ b/src/components/common/LoadingMask.tsx @@ -2,7 +2,7 @@ import React from 'react' import Icon from '@ant-design/icons' import '@/assets/css/components/common/loading-mask.scss' import FitFullScreen from '@/components/common/FitFullScreen' -import { COLOR_FONT_MAIN } from '@/constants/Common.constants' +import { COLOR_FONT_MAIN } from '@/constants/common.constants' const LoadingMask: React.FC = () => { const loadingIcon = ( diff --git a/src/constants/Common.constants.ts b/src/constants/Common.constants.ts deleted file mode 100644 index d7b32ad..0000000 --- a/src/constants/Common.constants.ts +++ /dev/null @@ -1,67 +0,0 @@ -const PRODUCTION_NAME = 'FatWeb' -const TOKEN_NAME = 'JWT_TOKEN' -const COLOR_PRODUCTION = '#00D4FF' -const COLOR_BACKGROUND = '#F5F5F5' -const COLOR_TOP = 'rgba(234,46,13,0.85)' -const COLOR_FONT_MAIN = '#4D4D4D' -const COLOR_FONT_SECONDARY = '#9E9E9E' -const SIZE_ICON_XS = '16px' -const SIZE_ICON_SM = '20px' -const SIZE_ICON_MD = '24px' -const SIZE_ICON_LG = '32px' -const SIZE_ICON_XL = '64px' - -/** - * Response code - */ -const SYSTEM_OK = 10000 -const SYSTEM_LOGIN_SUCCESS = 10020 -const SYSTEM_PASSWORD_CHANGE_SUCCESS = 10021 -const SYSTEM_LOGOUT_SUCCESS = 10022 -const SYSTEM_TOKEN_RENEW_SUCCESS = 10023 -const SYSTEM_UNAUTHORIZED = 10030 -const SYSTEM_USERNAME_NOT_FOUND = 10031 -const SYSTEM_ACCESS_DENIED = 10032 -const SYSTEM_USER_DISABLE = 10033 -const SYSTEM_LOGIN_USERNAME_PASSWORD_ERROR = 10034 -const SYSTEM_OLD_PASSWORD_NOT_MATCH = 10035 -const SYSTEM_LOGOUT_FAILED = 10036 -const SYSTEM_TOKEN_ILLEGAL = 10037 -const SYSTEM_TOKEN_HAS_EXPIRED = 10038 -const SYSTEM_REQUEST_ILLEGAL = 10040 -const SYSTEM_ARGUMENT_NOT_VALID = 10041 -const SYSTEM_ERROR = 10050 -const SYSTEM_TIMEOUT = 10051 - -export { - PRODUCTION_NAME, - TOKEN_NAME, - COLOR_PRODUCTION, - COLOR_BACKGROUND, - COLOR_FONT_MAIN, - COLOR_FONT_SECONDARY, - COLOR_TOP, - SIZE_ICON_XS, - SIZE_ICON_SM, - SIZE_ICON_MD, - SIZE_ICON_LG, - SIZE_ICON_XL, - SYSTEM_OK, - SYSTEM_LOGIN_SUCCESS, - SYSTEM_PASSWORD_CHANGE_SUCCESS, - SYSTEM_LOGOUT_SUCCESS, - SYSTEM_TOKEN_RENEW_SUCCESS, - SYSTEM_UNAUTHORIZED, - SYSTEM_USERNAME_NOT_FOUND, - SYSTEM_ACCESS_DENIED, - SYSTEM_USER_DISABLE, - SYSTEM_LOGIN_USERNAME_PASSWORD_ERROR, - SYSTEM_OLD_PASSWORD_NOT_MATCH, - SYSTEM_LOGOUT_FAILED, - SYSTEM_TOKEN_ILLEGAL, - SYSTEM_TOKEN_HAS_EXPIRED, - SYSTEM_REQUEST_ILLEGAL, - SYSTEM_ARGUMENT_NOT_VALID, - SYSTEM_ERROR, - SYSTEM_TIMEOUT -} diff --git a/src/constants/common.constants.ts b/src/constants/common.constants.ts new file mode 100644 index 0000000..6ca7f6a --- /dev/null +++ b/src/constants/common.constants.ts @@ -0,0 +1,35 @@ +export const PRODUCTION_NAME = 'FatWeb' +export const STORAGE_TOKEN_KEY = 'JWT_TOKEN' +export const STORAGE_USER_INFO_KEY = 'USER_INFO' +export const COLOR_PRODUCTION = '#00D4FF' +export const COLOR_BACKGROUND = '#F5F5F5' +export const COLOR_TOP = 'rgba(234,46,13,0.85)' +export const COLOR_FONT_MAIN = '#4D4D4D' +export const COLOR_FONT_SECONDARY = '#9E9E9E' +export const SIZE_ICON_XS = '16px' +export const SIZE_ICON_SM = '20px' +export const SIZE_ICON_MD = '24px' +export const SIZE_ICON_LG = '32px' +export const SIZE_ICON_XL = '64px' + +/** + * Response code + */ +export const SYSTEM_OK = 10000 +export const SYSTEM_LOGIN_SUCCESS = 10020 +export const SYSTEM_PASSWORD_CHANGE_SUCCESS = 10021 +export const SYSTEM_LOGOUT_SUCCESS = 10022 +export const SYSTEM_TOKEN_RENEW_SUCCESS = 10023 +export const SYSTEM_UNAUTHORIZED = 10030 +export const SYSTEM_USERNAME_NOT_FOUND = 10031 +export const SYSTEM_ACCESS_DENIED = 10032 +export const SYSTEM_USER_DISABLE = 10033 +export const SYSTEM_LOGIN_USERNAME_PASSWORD_ERROR = 10034 +export const SYSTEM_OLD_PASSWORD_NOT_MATCH = 10035 +export const SYSTEM_LOGOUT_FAILED = 10036 +export const SYSTEM_TOKEN_ILLEGAL = 10037 +export const SYSTEM_TOKEN_HAS_EXPIRED = 10038 +export const SYSTEM_REQUEST_ILLEGAL = 10040 +export const SYSTEM_ARGUMENT_NOT_VALID = 10041 +export const SYSTEM_ERROR = 10050 +export const SYSTEM_TIMEOUT = 10051 diff --git a/src/constants/urls.constants.ts b/src/constants/urls.constants.ts new file mode 100644 index 0000000..d9cd425 --- /dev/null +++ b/src/constants/urls.constants.ts @@ -0,0 +1,3 @@ +export const URL_API_LOGIN = '/login' +export const URL_API_TOKEN = '/token' +export const URL_API_LOGOUT = '/logout' diff --git a/src/pages/HomeFramework.tsx b/src/pages/HomeFramework.tsx index 56993c1..c09e00d 100644 --- a/src/pages/HomeFramework.tsx +++ b/src/pages/HomeFramework.tsx @@ -4,7 +4,7 @@ import router from '@/router' import LoadingMask from '@/components/common/LoadingMask' import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar' import Icon from '@ant-design/icons' -import { COLOR_FONT_SECONDARY } from '@/constants/Common.constants.ts' +import { COLOR_FONT_SECONDARY } from '@/constants/common.constants' import { NavLink } from 'react-router-dom' export const HomeFrameworkContext = createContext<{ diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 92a8bac..e4cb933 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,12 +1,12 @@ import React from 'react' -import { login } from '@/utils/auth.ts' +import { login } from '@/utils/auth' import { SYSTEM_LOGIN_SUCCESS, SYSTEM_LOGIN_USERNAME_PASSWORD_ERROR, SYSTEM_USER_DISABLE, SYSTEM_USERNAME_NOT_FOUND -} from '@/constants/Common.constants.ts' -import { setToken } from '@/utils/common.ts' +} from '@/constants/common.constants' +import { setToken } from '@/utils/common' import '@/assets/css/pages/login.scss' const Login: React.FC = () => { diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index c386c86..ebaa939 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -2,10 +2,10 @@ 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, getRedirectUrl, setLocalStorage } from '@/utils/common.ts' -import { getLoginStatus, logout } from '@/utils/auth.ts' +import { toolsJsonObjects } from '@/router/tools' +import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar' +import { getLocalStorage, getRedirectUrl, setLocalStorage } from '@/utils/common' +import { getLoginStatus, logout } from '@/utils/auth' import { NavLink, Outlet } from 'react-router-dom' const ToolsFramework: React.FC = () => { diff --git a/src/services/index.tsx b/src/services/index.tsx index 8aca097..8106e18 100644 --- a/src/services/index.tsx +++ b/src/services/index.tsx @@ -7,7 +7,7 @@ import { SYSTEM_TOKEN_ILLEGAL, SYSTEM_TOKEN_RENEW_SUCCESS, SYSTEM_UNAUTHORIZED -} from '@/constants/Common.constants' +} from '@/constants/common.constants' import { message } from 'antd' const service: AxiosInstance = axios.create({ diff --git a/src/utils/auth.ts b/src/utils/auth.ts index c2f3155..f58a9ac 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,30 +1,31 @@ import { getCaptcha, getLocalStorage, removeToken, setLocalStorage } from './common' -import { SYSTEM_OK, TOKEN_NAME } from '@/constants/Common.constants' +import { SYSTEM_OK, STORAGE_TOKEN_KEY, STORAGE_USER_INFO_KEY } from '@/constants/common.constants' import request from '@/services' +import { URL_API_LOGIN, URL_API_LOGOUT } from '@/constants/urls.constants' let captcha: Captcha export const login = async (username: string, password: string) => { - return await request.post('/login', { + return await request.post(URL_API_LOGIN, { username, password }) } export const logout = async () => { - return request.post('/logout').finally(() => { + return request.post(URL_API_LOGOUT).finally(() => { removeToken() }) } export const getLoginStatus = () => { - return getLocalStorage(TOKEN_NAME) !== null + return getLocalStorage(STORAGE_TOKEN_KEY) !== null } export const getUser = async (): Promise => { - if (getLocalStorage('userInfo') !== null) { + if (getLocalStorage(STORAGE_USER_INFO_KEY) !== null) { return new Promise((resolve) => { - resolve(JSON.parse(getLocalStorage('userInfo') as string) as User) + resolve(JSON.parse(getLocalStorage(STORAGE_USER_INFO_KEY) as string) as User) }) } return requestUser() @@ -37,7 +38,7 @@ export const requestUser = async () => { const response = value.data if (response.code === SYSTEM_OK) { user = response.data - setLocalStorage('userInfo', JSON.stringify(user)) + setLocalStorage(STORAGE_USER_INFO_KEY, JSON.stringify(user)) } }) diff --git a/src/utils/common.ts b/src/utils/common.ts index c74b23e..ebae83a 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,4 +1,4 @@ -import { TOKEN_NAME } from '@/constants/Common.constants' +import { STORAGE_TOKEN_KEY, STORAGE_USER_INFO_KEY } from '@/constants/common.constants' export const getQueryVariable = (variable: string) => { const query = window.location.search.substring(1) @@ -36,7 +36,7 @@ export const setLocalStorage = (name: string, value: string) => { } export const setToken = (token: string) => { - setLocalStorage(TOKEN_NAME, token) + setLocalStorage(STORAGE_TOKEN_KEY, token) } export const getCookie = (name: string) => { @@ -57,7 +57,7 @@ export const getLocalStorage = (name: string) => { } export const getToken = () => { - return getLocalStorage(TOKEN_NAME) + return getLocalStorage(STORAGE_TOKEN_KEY) } export const removeCookie = (name: string) => { @@ -69,8 +69,8 @@ export const removeLocalStorage = (name: string) => { } export const removeToken = () => { - removeLocalStorage('userInfo') - removeLocalStorage(TOKEN_NAME) + removeLocalStorage(STORAGE_USER_INFO_KEY) + removeLocalStorage(STORAGE_TOKEN_KEY) } export const clearLocalStorage = () => { From a0903274297edacd33dc92202fbc468eb22f91a7 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Mon, 16 Oct 2023 15:31:58 +0800 Subject: [PATCH 042/307] Add exit waiting to ToolsFramework --- src/pages/ToolsFramework.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index ebaa939..c997eff 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -17,6 +17,7 @@ const ToolsFramework: React.FC = () => { const [submenuTop, setSubmenuTop] = useState(0) const [submenuLeft, setSubmenuLeft] = useState(0) const [hideSidebar, setHideSidebar] = useState(getLocalStorage('hideSidebar') === 'false') + const [exiting, setExiting] = useState(false) const switchSidebar = () => { setHideSidebar(!hideSidebar) @@ -52,6 +53,11 @@ const ToolsFramework: React.FC = () => { } const handleLogout = () => { + if (exiting) { + return + } + + setExiting(true) void logout().finally(() => { setTimeout(() => { window.location.reload() @@ -232,7 +238,10 @@ const ToolsFramework: React.FC = () => { className={'icon-exit'} onClick={handleLogout} > - +
From 75962aafc9721f3a9575152b533ad2d2519f8e1f Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Tue, 17 Oct 2023 11:32:39 +0800 Subject: [PATCH 043/307] Optimize router --- src/global.d.ts | 7 +-- src/pages/ToolsFramework.tsx | 16 +++---- src/router/home.tsx | 34 ++++++++++++++ src/router/index.tsx | 89 ++++++++++++++++-------------------- src/router/tools.tsx | 42 ++--------------- 5 files changed, 91 insertions(+), 97 deletions(-) create mode 100644 src/router/home.tsx diff --git a/src/global.d.ts b/src/global.d.ts index e64304b..36438e0 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -10,9 +10,10 @@ interface ImportMeta { readonly env: ImportMetaEnv } -type ToolsJsonObject = { +type RouteJsonObject = { path: string - id: string + id?: string + element?: React.JSX.Element component?: React.ComponentType name?: string titlePrefix?: string @@ -21,7 +22,7 @@ type ToolsJsonObject = { icon?: IconComponent menu?: boolean auth?: boolean - children?: ToolsJsonObject[] + children?: RouteJsonObject[] } type RouteHandle = { diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index c997eff..27d51cc 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -2,7 +2,7 @@ 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' +import { tools } from '@/router/tools' import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar' import { getLocalStorage, getRedirectUrl, setLocalStorage } from '@/utils/common' import { getLoginStatus, logout } from '@/utils/auth' @@ -88,14 +88,14 @@ const ToolsFramework: React.FC = () => { } >
- {toolsJsonObjects[0].icon ? ( + {tools[0].icon ? ( ) : undefined}
- {toolsJsonObjects[0].name} + {tools[0].name}
@@ -108,14 +108,14 @@ const ToolsFramework: React.FC = () => { } >
- {toolsJsonObjects[1].icon ? ( + {tools[1].icon ? ( ) : undefined}
- {toolsJsonObjects[1].name} + {tools[1].name}
@@ -132,7 +132,7 @@ const ToolsFramework: React.FC = () => { ref={hideScrollbarRef} >
    - {toolsJsonObjects.map((tool) => { + {tools.map((tool) => { return tool.menu && tool.id !== 'tools' && tool.id !== 'tools-all' ? ( diff --git a/src/router/home.tsx b/src/router/home.tsx new file mode 100644 index 0000000..50792a6 --- /dev/null +++ b/src/router/home.tsx @@ -0,0 +1,34 @@ +import React from 'react' + +const home: RouteJsonObject[] = [ + { + path: '', + id: 'home', + component: React.lazy(() => import('@/pages/home')), + name: '主页', + menu: true, + auth: false + }, + { + path: 'https://blog.fatweb.top', + id: 'url-blog', + name: '博客', + menu: true + }, + { + path: '/tools', + id: 'url-tools', + children: [ + { + path: 'translation', + id: 'url-tools-translation', + name: '翻译', + menu: true + } + ], + name: '工具', + menu: true + } +] + +export default home diff --git a/src/router/index.tsx b/src/router/index.tsx index ea360d9..2259f62 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -1,74 +1,36 @@ import React from 'react' import tools from '@/router/tools' +import home from '@/router/home' -const routes: RouteObject[] = [ +const root: RouteJsonObject[] = [ { path: '/', - Component: React.lazy(() => import('@/AuthRoute')), + component: React.lazy(() => import('@/AuthRoute')), children: [ { path: '/login', id: 'login', - Component: React.lazy(() => import('@/pages/Login')) + component: React.lazy(() => import('@/pages/Login')) }, { path: '/loading', id: 'loading', - Component: React.lazy(() => import('@/components/common/LoadingMask')) + component: React.lazy(() => import('@/components/common/LoadingMask')) }, { path: '/tools', id: 'toolsFramework', - Component: React.lazy(() => import('@/pages/ToolsFramework')), + component: React.lazy(() => import('@/pages/ToolsFramework')), children: tools, - handle: { - name: '工具', - title: '工具', - auth: false - } + name: '工具', + title: '工具', + auth: false }, { path: '', id: 'homeFramework', - Component: React.lazy(() => import('@/pages/HomeFramework')), - children: [ - { - path: '', - id: 'home', - Component: React.lazy(() => import('@/pages/home')), - handle: { - name: '主页', - menu: true, - auth: false - } - }, - { - path: 'https://blog.fatweb.top', - id: 'url-blog', - handle: { - name: '博客', - menu: true - } - }, - { - path: '/tools', - id: 'url-tools', - children: [ - { - path: 'translation', - id: 'url-tools-translation', - handle: { - name: '翻译', - menu: true - } - } - ], - handle: { - name: '工具', - menu: true - } - } - ] + component: React.lazy(() => import('@/pages/HomeFramework')), + children: home }, { path: '*', @@ -78,5 +40,34 @@ const routes: RouteObject[] = [ } ] +const mapJsonToRoute = (jsonObject: RouteJsonObject[]): RouteObject[] => { + return jsonObject.map((value) => ({ + path: value.path, + id: value.id, + element: value.element, + Component: value.component, + handle: { + name: value.name, + titlePrefix: value.titlePrefix, + title: value.title, + titlePostfix: value.titlePostfix, + icon: value.icon, + menu: value.menu, + auth: value.auth + }, + children: + value.children && + mapJsonToRoute(value.children).map((sub) => { + const handle = sub.handle as RouteHandle + if (!handle.title) { + handle.title = value.title + } + return sub + }) + })) +} + +const routes = mapJsonToRoute(root) + const router = createBrowserRouter(routes) export default router diff --git a/src/router/tools.tsx b/src/router/tools.tsx index 997809a..89a38ff 100644 --- a/src/router/tools.tsx +++ b/src/router/tools.tsx @@ -1,8 +1,6 @@ import React from 'react' -const defaultTitle = '氮工具' - -export const toolsJsonObjects: ToolsJsonObject[] = [ +export const tools: RouteJsonObject[] = [ { path: '', id: 'tools', @@ -277,41 +275,11 @@ export const toolsJsonObjects: ToolsJsonObject[] = [ auth: false } ] + }, + { + path: '*', + element: } ] -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 - } - })) -})) - -tools.push({ - path: '*', - element: -}) - export default tools From 21450cc753157fdb2bf84720076d4e5e30366be2 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Tue, 17 Oct 2023 11:49:57 +0800 Subject: [PATCH 044/307] Optimize router --- src/router/index.tsx | 60 +++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/router/index.tsx b/src/router/index.tsx index 2259f62..da0ff94 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -2,6 +2,36 @@ import React from 'react' import tools from '@/router/tools' import home from '@/router/home' +const mapJsonToRoute = (jsonObject: RouteJsonObject[]): RouteObject[] => { + return jsonObject.map((value) => ({ + path: value.path, + id: value.id, + element: value.element, + Component: value.component, + handle: { + name: value.name, + titlePrefix: value.titlePrefix, + title: value.title, + titlePostfix: value.titlePostfix, + icon: value.icon, + menu: value.menu, + auth: value.auth + }, + children: value.children && mapJsonToRoute(value.children) + })) +} + +const setTitle = (jsonObject: RouteJsonObject[], title: string): RouteJsonObject[] => { + return jsonObject.map((value) => { + if (!value.title) { + value.title = title + } + value.children && setTitle(value.children, title) + + return value + }) +} + const root: RouteJsonObject[] = [ { path: '/', @@ -21,9 +51,8 @@ const root: RouteJsonObject[] = [ path: '/tools', id: 'toolsFramework', component: React.lazy(() => import('@/pages/ToolsFramework')), - children: tools, + children: setTitle(tools, '氮工具'), name: '工具', - title: '工具', auth: false }, { @@ -40,33 +69,6 @@ const root: RouteJsonObject[] = [ } ] -const mapJsonToRoute = (jsonObject: RouteJsonObject[]): RouteObject[] => { - return jsonObject.map((value) => ({ - path: value.path, - id: value.id, - element: value.element, - Component: value.component, - handle: { - name: value.name, - titlePrefix: value.titlePrefix, - title: value.title, - titlePostfix: value.titlePostfix, - icon: value.icon, - menu: value.menu, - auth: value.auth - }, - children: - value.children && - mapJsonToRoute(value.children).map((sub) => { - const handle = sub.handle as RouteHandle - if (!handle.title) { - handle.title = value.title - } - return sub - }) - })) -} - const routes = mapJsonToRoute(root) const router = createBrowserRouter(routes) From 491fb75be8a0038fdba69b07eddf8c09ba06e56c Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Tue, 17 Oct 2023 17:42:34 +0800 Subject: [PATCH 045/307] Optimize import --- src/AuthRoute.tsx | 2 +- src/pages/HomeFramework.tsx | 7 +++---- src/pages/Login.tsx | 4 ++-- src/services/index.tsx | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/AuthRoute.tsx b/src/AuthRoute.tsx index c3e3cda..c131a0b 100644 --- a/src/AuthRoute.tsx +++ b/src/AuthRoute.tsx @@ -1,5 +1,5 @@ -import { getLoginStatus } from '@/utils/auth' import { PRODUCTION_NAME } from '@/constants/common.constants' +import { getLoginStatus } from '@/utils/auth' import { getRedirectUrl } from '@/utils/common' const AuthRoute = () => { diff --git a/src/pages/HomeFramework.tsx b/src/pages/HomeFramework.tsx index c09e00d..b16e2ec 100644 --- a/src/pages/HomeFramework.tsx +++ b/src/pages/HomeFramework.tsx @@ -1,11 +1,10 @@ import React from 'react' -import '@/assets/css/pages/home-framework.scss' +import Icon from '@ant-design/icons' import router from '@/router' +import { COLOR_FONT_SECONDARY } from '@/constants/common.constants' +import '@/assets/css/pages/home-framework.scss' import LoadingMask from '@/components/common/LoadingMask' import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar' -import Icon from '@ant-design/icons' -import { COLOR_FONT_SECONDARY } from '@/constants/common.constants' -import { NavLink } from 'react-router-dom' export const HomeFrameworkContext = createContext<{ navbarHiddenState: { diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index e4cb933..0efc6a7 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,13 +1,13 @@ import React from 'react' -import { login } from '@/utils/auth' import { SYSTEM_LOGIN_SUCCESS, SYSTEM_LOGIN_USERNAME_PASSWORD_ERROR, SYSTEM_USER_DISABLE, SYSTEM_USERNAME_NOT_FOUND } from '@/constants/common.constants' -import { setToken } from '@/utils/common' import '@/assets/css/pages/login.scss' +import { setToken } from '@/utils/common' +import { login } from '@/utils/auth' const Login: React.FC = () => { const [messageApi, contextHolder] = message.useMessage() diff --git a/src/services/index.tsx b/src/services/index.tsx index 8106e18..d509f81 100644 --- a/src/services/index.tsx +++ b/src/services/index.tsx @@ -1,5 +1,6 @@ import axios, { type AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' import jwtDecode, { JwtPayload } from 'jwt-decode' +import { message } from 'antd' import { getToken, removeToken, setToken } from '@/utils/common' import { SYSTEM_ACCESS_DENIED, @@ -8,7 +9,6 @@ import { SYSTEM_TOKEN_RENEW_SUCCESS, SYSTEM_UNAUTHORIZED } from '@/constants/common.constants' -import { message } from 'antd' const service: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_URL, From 14380d42dce322c12541e6ca62ea8f3b5d1f7a11 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Tue, 17 Oct 2023 17:46:52 +0800 Subject: [PATCH 046/307] Add Sidebar component --- src/assets/css/components/common/sidebar.scss | 301 ++++++++++++++++++ src/assets/css/pages/tools-framework.scss | 288 ----------------- .../common/sidebar/SidebarFooter.tsx | 64 ++++ src/components/common/sidebar/SidebarItem.tsx | 60 ++++ .../common/sidebar/SidebarItemList.tsx | 7 + .../common/sidebar/SidebarScroll.tsx | 34 ++ .../common/sidebar/SidebarSeparate.tsx | 11 + .../common/sidebar/SidebarSubmenu.tsx | 22 ++ src/components/common/sidebar/index.tsx | 44 +++ src/pages/ToolsFramework.tsx | 290 ++++------------- 10 files changed, 597 insertions(+), 524 deletions(-) create mode 100644 src/assets/css/components/common/sidebar.scss create mode 100644 src/components/common/sidebar/SidebarFooter.tsx create mode 100644 src/components/common/sidebar/SidebarItem.tsx create mode 100644 src/components/common/sidebar/SidebarItemList.tsx create mode 100644 src/components/common/sidebar/SidebarScroll.tsx create mode 100644 src/components/common/sidebar/SidebarSeparate.tsx create mode 100644 src/components/common/sidebar/SidebarSubmenu.tsx create mode 100644 src/components/common/sidebar/index.tsx diff --git a/src/assets/css/components/common/sidebar.scss b/src/assets/css/components/common/sidebar.scss new file mode 100644 index 0000000..b94d972 --- /dev/null +++ b/src/assets/css/components/common/sidebar.scss @@ -0,0 +1,301 @@ +@use "@/assets/css/constants" as constants; +@use "@/assets/css/mixins" as mixins; + +.sidebar { + display: flex; + flex-direction: column; + height: 100%; + user-select: none; + transition: all .3s; + white-space: nowrap; + + .title { + display: flex; + align-items: center; + font-weight: bold; + padding: 10px 14px; + color: constants.$main-color; + overflow: hidden; + + .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.2em; + transform: translateX(0.1em); + } + } + + .content { + display: flex; + min-height: 0; + flex-direction: column; + flex: 1; + + .scroll { + 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% { + transform: translateX(-10px); + opacity: 0; + } + 100% { + 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-user { + display: flex; + justify-content: center; + align-items: center; + margin-left: 4px; + padding: 10px; + width: 36px; + height: 36px; + 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: 10px; + font-size: 1.4em; + color: constants.$font-main-color; + user-select: text; + + a{ + color: constants.$main-color; + text-decoration: underline; + } + } + + .icon-exit { + font-size: constants.$SIZE_ICON_XS; + color: constants.$error-color; + padding: 6px 10px; + cursor: pointer; + + &:hover { + border-radius: 8px; + background-color: constants.$background-color; + } + } + } + + &.hide { + width: 68px !important; + + .title { + .icon-box { + span { + transform: rotateZ(360deg); + transition: all .3s; + } + } + .text { + display: none; + } + } + + .menu-bt { + .text { + display: none; + } + } + + .submenu { + .menu-bt { + .text { + display: block; + } + } + } + + .footer { + position: relative; + .text { + display: none; + } + + .submenu-exit { + display: none; + position: absolute; + padding-left: 6px; + left: 100%; + + .content { + padding: 8px; + border-radius: 8px; + background-color: constants.$origin-color; + + .icon-exit { + padding: 4px 8px; + &:hover { + border-radius: 8px; + background-color: constants.$background-color; + } + } + } + + &.hide { + display: none!important; + } + } + + &:hover .submenu-exit { + display: block; + animation: 0.3s ease; + @include mixins.unique-keyframes { + 0% { + transform: translateX(-10px); + opacity: 0; + } + 100% { + transform: translateX(0); + opacity: 1; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/assets/css/pages/tools-framework.scss b/src/assets/css/pages/tools-framework.scss index f9e08cf..9906a03 100644 --- a/src/assets/css/pages/tools-framework.scss +++ b/src/assets/css/pages/tools-framework.scss @@ -6,295 +6,7 @@ body { } .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; - - .title { - display: flex; - align-items: center; - font-weight: bold; - padding: 10px 14px; - color: constants.$main-color; - overflow: hidden; - - .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% { - transform: translateX(-10px); - opacity: 0; - } - 100% { - 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-user { - display: flex; - justify-content: center; - align-items: center; - margin-left: 4px; - padding: 10px; - width: 36px; - height: 36px; - 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: 10px; - font-size: 1.4em; - color: constants.$font-main-color; - user-select: text; - - a{ - color: constants.$main-color; - text-decoration: underline; - } - } - - .icon-exit { - font-size: constants.$SIZE_ICON_XS; - color: constants.$error-color; - padding: 6px 10px; - cursor: pointer; - - &:hover { - border-radius: 8px; - background-color: constants.$background-color; - } - } - } - - &.hide { - width: 68px; - - .title { - .icon-box { - span { - transform: rotateZ(360deg); - transition: all .3s; - } - } - .text { - display: none; - } - } - - .menu-bt { - .text { - display: none; - } - } - - .footer { - position: relative; - .text { - display: none; - } - - .submenu-exit { - display: none; - position: absolute; - padding-left: 6px; - left: 100%; - - .content { - padding: 8px; - border-radius: 8px; - background-color: constants.$origin-color; - - .icon-exit { - padding: 4px 8px; - &:hover { - border-radius: 8px; - background-color: constants.$background-color; - } - } - } - - &.hide { - display: none!important; - } - } - - &:hover .submenu-exit { - display: block; - animation: 0.3s ease; - @include mixins.unique-keyframes { - 0% { - transform: translateX(-10px); - opacity: 0; - } - 100% { - transform: translateX(0); - opacity: 1; - } - } - } - } - } } .right-panel { diff --git a/src/components/common/sidebar/SidebarFooter.tsx b/src/components/common/sidebar/SidebarFooter.tsx new file mode 100644 index 0000000..55e1d24 --- /dev/null +++ b/src/components/common/sidebar/SidebarFooter.tsx @@ -0,0 +1,64 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { getLoginStatus, logout } from '@/utils/auth' +import { getRedirectUrl } from '@/utils/common' + +const SidebarFooter: React.FC = () => { + const matches = useMatches() + const lastMatch = matches.reduce((_, second) => second) + const location = useLocation() + const navigate = useNavigate() + const [exiting, setExiting] = useState(false) + const handleClickAvatar = () => { + if (getLoginStatus()) { + navigate('/user') + } else { + navigate(getRedirectUrl('/login', `${lastMatch.pathname}${location.search}`)) + } + } + + const handleLogout = () => { + if (exiting) { + return + } + + setExiting(true) + void logout().finally(() => { + setTimeout(() => { + window.location.reload() + }, 1500) + }) + } + + return ( +
    + + + + + + +
    + ) +} + +export default SidebarFooter diff --git a/src/components/common/sidebar/SidebarItem.tsx b/src/components/common/sidebar/SidebarItem.tsx new file mode 100644 index 0000000..4fff29d --- /dev/null +++ b/src/components/common/sidebar/SidebarItem.tsx @@ -0,0 +1,60 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import SidebarSubmenu from '@/components/common/sidebar/SidebarSubmenu' + +type ItemProps = { + icon?: IconComponent + text?: string + path: string + children?: React.ReactNode +} + +const SidebarItem: React.FC = (props) => { + const [submenuTop, setSubmenuTop] = useState(0) + const [submenuLeft, setSubmenuLeft] = useState(0) + + 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' : '' + } + > +
    + {props.icon ? ( + + ) : undefined} +
    + {props.text} +
    +
    + {props.children ? ( + + {props.children} + + ) : undefined} +
  • + ) +} + +export default SidebarItem diff --git a/src/components/common/sidebar/SidebarItemList.tsx b/src/components/common/sidebar/SidebarItemList.tsx new file mode 100644 index 0000000..42337e1 --- /dev/null +++ b/src/components/common/sidebar/SidebarItemList.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +const SidebarItemList: React.FC = (props) => { + return
      {props.children}
    +} + +export default SidebarItemList diff --git a/src/components/common/sidebar/SidebarScroll.tsx b/src/components/common/sidebar/SidebarScroll.tsx new file mode 100644 index 0000000..8690664 --- /dev/null +++ b/src/components/common/sidebar/SidebarScroll.tsx @@ -0,0 +1,34 @@ +import React, { useImperativeHandle } from 'react' +import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar' + +export interface SidebarScrollElement { + refreshLayout(): void +} + +const SidebarScroll = forwardRef((props, ref) => { + useImperativeHandle(ref, () => { + return { + refreshLayout() { + hideScrollbarRef.current?.refreshLayout() + } + } + }) + + const hideScrollbarRef = useRef(null) + + return ( +
    + + {props.children} + +
    + ) +}) + +export default SidebarScroll diff --git a/src/components/common/sidebar/SidebarSeparate.tsx b/src/components/common/sidebar/SidebarSeparate.tsx new file mode 100644 index 0000000..3887824 --- /dev/null +++ b/src/components/common/sidebar/SidebarSeparate.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +const SidebarSeparate: React.FC< + React.DetailedHTMLProps, HTMLDivElement> +> = (props) => { + const { className, ..._props } = props + + return
    +} + +export default SidebarSeparate diff --git a/src/components/common/sidebar/SidebarSubmenu.tsx b/src/components/common/sidebar/SidebarSubmenu.tsx new file mode 100644 index 0000000..ed2039b --- /dev/null +++ b/src/components/common/sidebar/SidebarSubmenu.tsx @@ -0,0 +1,22 @@ +import React from 'react' + +interface SidebarSubmenuProps extends React.PropsWithChildren { + submenuTop: number + submenuLeft: number +} + +const SidebarSubmenu: React.FC = (props) => { + return ( +
      +
      {props.children}
      +
    + ) +} + +export default SidebarSubmenu diff --git a/src/components/common/sidebar/index.tsx b/src/components/common/sidebar/index.tsx new file mode 100644 index 0000000..41f1006 --- /dev/null +++ b/src/components/common/sidebar/index.tsx @@ -0,0 +1,44 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import '@/assets/css/components/common/sidebar.scss' +import SidebarSeparate from '@/components/common/sidebar/SidebarSeparate' +import SidebarFooter from '@/components/common/sidebar/SidebarFooter' +import { getLocalStorage, setLocalStorage } from '@/utils/common' + +interface SidebarProps extends React.PropsWithChildren { + title: string + width?: string + onSidebarSwitch?: (hidden: boolean) => void +} + +const Sidebar: React.FC = (props) => { + const [hideSidebar, setHideSidebar] = useState(getLocalStorage('hideSidebar') === 'false') + + const switchSidebar = () => { + setHideSidebar(!hideSidebar) + setLocalStorage('hideSidebar', hideSidebar ? 'true' : 'false') + props.onSidebarSwitch && props.onSidebarSwitch(hideSidebar) + } + + return ( + <> +
    +
    + + + + {props.title} +
    + +
    {props.children}
    + + +
    + + ) +} + +export default Sidebar diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index 27d51cc..8930fd5 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -1,251 +1,69 @@ import React from 'react' -import FitFullScreen from '@/components/common/FitFullScreen' -import '@/assets/css/pages/tools-framework.scss' -import Icon from '@ant-design/icons' import { tools } from '@/router/tools' -import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar' -import { getLocalStorage, getRedirectUrl, setLocalStorage } from '@/utils/common' -import { getLoginStatus, logout } from '@/utils/auth' -import { NavLink, Outlet } from 'react-router-dom' +import '@/assets/css/pages/tools-framework.scss' +import FitFullScreen from '@/components/common/FitFullScreen' +import SidebarScroll, { SidebarScrollElement } from '@/components/common/sidebar/SidebarScroll' +import Sidebar from '@/components/common/sidebar' +import SidebarItemList from '@/components/common/sidebar/SidebarItemList' +import SidebarItem from '@/components/common/sidebar/SidebarItem' +import SidebarSeparate from '@/components/common/sidebar/SidebarSeparate' const ToolsFramework: React.FC = () => { - const matches = useMatches() - const lastMatch = matches.reduce((_, second) => second) - const location = useLocation() - const navigate = useNavigate() - const hideScrollbarRef = useRef(null) - const [submenuTop, setSubmenuTop] = useState(0) - const [submenuLeft, setSubmenuLeft] = useState(0) - const [hideSidebar, setHideSidebar] = useState(getLocalStorage('hideSidebar') === 'false') - const [exiting, setExiting] = useState(false) + const sidebarScrollRef = useRef(null) - const switchSidebar = () => { - setHideSidebar(!hideSidebar) - setLocalStorage('hideSidebar', hideSidebar ? 'true' : 'false') + const handleOnSidebarSwitch = () => { setTimeout(() => { - hideScrollbarRef.current?.refreshLayout() + sidebarScrollRef.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) - } - } - - const handleClickAvatar = () => { - if (getLoginStatus()) { - navigate(`${lastMatch.pathname}${location.search}`) - } else { - navigate(getRedirectUrl('/login', `${lastMatch.pathname}${location.search}`)) - } - } - - const handleLogout = () => { - if (exiting) { - return - } - - setExiting(true) - void logout().finally(() => { - setTimeout(() => { - window.location.reload() - }, 1500) - }) - } - return ( <> -
    -
    - - - - 氮工具 -
    -
    -
    -
      -
    • -
      - - isPending ? 'pending' : isActive ? 'active' : '' - } - > -
      - {tools[0].icon ? ( - - ) : undefined} -
      - {tools[0].name} -
      -
      -
    • -
    • -
      - - isPending ? ' pending' : isActive ? ' active' : '' - } - > -
      - {tools[1].icon ? ( - - ) : undefined} -
      - {tools[1].name} -
      -
      -
    • -
    • -
      -
    • -
    -
    - -
      - {tools.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 - })} -
    -
    -
    -
    -
    -
    - - - - - - -
    +
    + + + + + + + + + {tools.map((tool) => { + return tool.menu && + tool.id !== 'tools' && + tool.id !== 'tools-all' ? ( + + {tool.children + ? tool.children.map((subTool) => { + return ( + + ) + }) + : undefined} + + ) : undefined + })} + + +
    From 8c1bd15dc279151fa2bac0738f078d2a97696353 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Tue, 17 Oct 2023 18:29:28 +0800 Subject: [PATCH 047/307] Fix union key in ToolsFramework --- src/pages/ToolsFramework.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ToolsFramework.tsx b/src/pages/ToolsFramework.tsx index 8930fd5..9f05bec 100644 --- a/src/pages/ToolsFramework.tsx +++ b/src/pages/ToolsFramework.tsx @@ -53,7 +53,7 @@ const ToolsFramework: React.FC = () => { ) }) From 5d71433d630c5c175c8953712ebd9cf6e1e9b7db Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Tue, 17 Oct 2023 18:33:21 +0800 Subject: [PATCH 048/307] Add UserFramework --- src/pages/UserFramework.tsx | 37 +++++++++++++++++++++++++++++++++++++ src/router/index.tsx | 9 +++++++++ src/router/user.tsx | 17 +++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 src/pages/UserFramework.tsx create mode 100644 src/router/user.tsx diff --git a/src/pages/UserFramework.tsx b/src/pages/UserFramework.tsx new file mode 100644 index 0000000..3b66b40 --- /dev/null +++ b/src/pages/UserFramework.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import FitFullScreen from '@/components/common/FitFullScreen' +import '@/assets/css/pages/tools-framework.scss' +import Sidebar from '@/components/common/sidebar' +import SidebarItemList from '@/components/common/sidebar/SidebarItemList' +import SidebarItem from '@/components/common/sidebar/SidebarItem' +import user from '@/router/user.tsx' + +const ToolsFramework: React.FC = () => { + return ( + <> + +
    + + + {user.map((value) => { + return value.menu ? ( + + ) : undefined + })} + + +
    +
    + +
    +
    + + ) +} + +export default ToolsFramework diff --git a/src/router/index.tsx b/src/router/index.tsx index da0ff94..c570281 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -1,6 +1,7 @@ import React from 'react' import tools from '@/router/tools' import home from '@/router/home' +import user from '@/router/user.tsx' const mapJsonToRoute = (jsonObject: RouteJsonObject[]): RouteObject[] => { return jsonObject.map((value) => ({ @@ -55,6 +56,14 @@ const root: RouteJsonObject[] = [ name: '工具', auth: false }, + { + path: '/user', + id: 'userFramework', + component: React.lazy(() => import('@/pages/UserFramework')), + children: setTitle(user, '个人中心'), + name: '个人中心', + auth: true + }, { path: '', id: 'homeFramework', diff --git a/src/router/user.tsx b/src/router/user.tsx new file mode 100644 index 0000000..ee8c550 --- /dev/null +++ b/src/router/user.tsx @@ -0,0 +1,17 @@ +import React from 'react' + +const user: RouteJsonObject[] = [ + { + path: '', + id: 'user', + name: '个人档案', + icon: React.lazy(() => import('~icons/fatweb/user.jsx')), + menu: true + }, + { + path: '*', + element: + } +] + +export default user From fcd4fd532c6efe161a00b3f49f8060566035a476 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Mon, 30 Oct 2023 00:26:19 +0800 Subject: [PATCH 049/307] Add login and logout notification --- package-lock.json | 9 ++++ package.json | 1 + src/assets/css/constants.scss | 11 ++--- .../common/sidebar/SidebarFooter.tsx | 22 +++++++++- src/constants/common.constants.ts | 19 ++++++++- src/constants/urls.constants.ts | 1 + src/global.d.ts | 41 +++++++++++++++++-- src/pages/Login.tsx | 25 ++++++++++- src/services/index.tsx | 2 +- src/utils/auth.ts | 28 +++++++------ 10 files changed, 134 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index c67c05d..159998f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "localforage": "^1.10.0", "lodash": "^4.17.21", "match-sorter": "^6.3.1", + "moment": "^2.29.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^6.15.0", @@ -4885,6 +4886,14 @@ "ufo": "^1.3.0" } }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", diff --git a/package.json b/package.json index 288a28a..932e59b 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "localforage": "^1.10.0", "lodash": "^4.17.21", "match-sorter": "^6.3.1", + "moment": "^2.29.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^6.15.0", diff --git a/src/assets/css/constants.scss b/src/assets/css/constants.scss index 3e6e1b5..50786e0 100644 --- a/src/assets/css/constants.scss +++ b/src/assets/css/constants.scss @@ -1,10 +1,11 @@ $origin-color: white; -$main-color: #4E47BB; +$production-color: #4E47BB; +$main-color: $production-color; $secondary-color: #BAB8E5; -$error-color: #ff4d4f; -$error-secondary-color: #ff7875; -$blue-color: #1677ff; -$active-color: #EBECFFD; +$error-color: #FF4D4F; +$error-secondary-color: #FF7875; +$blue-color: #1677FF; +$active-color: #EBECFD; $background-color: #F5F5F5; $font-main-color: #4D4D4D; $font-secondary-color: #9E9E9E; diff --git a/src/components/common/sidebar/SidebarFooter.tsx b/src/components/common/sidebar/SidebarFooter.tsx index 55e1d24..c3b7666 100644 --- a/src/components/common/sidebar/SidebarFooter.tsx +++ b/src/components/common/sidebar/SidebarFooter.tsx @@ -1,7 +1,9 @@ import React from 'react' import Icon from '@ant-design/icons' -import { getLoginStatus, logout } from '@/utils/auth' +import { getLoginStatus, getUsername, logout } from '@/utils/auth' import { getRedirectUrl } from '@/utils/common' +import { notification } from 'antd' +import { COLOR_ERROR } from '@/constants/common.constants.ts' const SidebarFooter: React.FC = () => { const matches = useMatches() @@ -9,6 +11,8 @@ const SidebarFooter: React.FC = () => { const location = useLocation() const navigate = useNavigate() const [exiting, setExiting] = useState(false) + const [username, setUsername] = useState('') + const handleClickAvatar = () => { if (getLoginStatus()) { navigate('/user') @@ -24,12 +28,26 @@ const SidebarFooter: React.FC = () => { setExiting(true) void logout().finally(() => { + notification.info({ + message: '已退出登录', + icon: + }) setTimeout(() => { window.location.reload() }, 1500) }) } + const loginStatus = getLoginStatus() + + useEffect(() => { + if (getLoginStatus()) { + void getUsername().then((username) => { + setUsername(username) + }) + } + }, [loginStatus]) + return (
    @@ -42,7 +60,7 @@ const SidebarFooter: React.FC = () => {