Add ToolsFramework #32

Merged
FatttSnake merged 26 commits from FatttSnake into dev 2023-10-14 18:36:50 +08:00
37 changed files with 984 additions and 98 deletions

View File

@@ -1,6 +1,6 @@
import React from 'react'
import router from '@/router'
import LoadingMask from '@/components/common/LoadingMask.tsx'
import LoadingMask from '@/components/common/LoadingMask'
const App: React.FC = () => {
return (

View File

@@ -1,20 +1,41 @@
import { getLoginStatus } from '@/utils/auth.ts'
import { PRODUCTION_NAME } from '@/constants/Common.constants.ts'
const AuthRoute = () => {
const match = useMatches()[1]
const handle = match.handle as RouteHandle
const matches = useMatches()
const lastMatch = matches.reduce((_, second) => second)
const handle = lastMatch.handle as RouteHandle
const location = useLocation()
const outlet = useOutlet()
const isLogin = getLoginStatus()
return useMemo(() => {
if (handle?.auth && !isLogin) {
return <Navigate to="/login" />
document.title = `${handle?.titlePrefix ?? ''}${
handle?.title ? handle?.title : PRODUCTION_NAME
}${handle?.titlePostfix ?? ''}`
if (matches.some(({ handle }) => (handle as RouteHandle)?.auth) && !isLogin) {
return (
<Navigate
to={`/login?redirect=${encodeURIComponent(
`${lastMatch.pathname}${location.search}`
)}`}
/>
)
}
if (isLogin && match.pathname === '/login') {
if (isLogin && lastMatch.pathname === '/login') {
return <Navigate to="/" />
}
return outlet
}, [handle?.auth, isLogin, match.pathname, outlet])
}, [
handle?.title,
handle?.titlePostfix,
handle?.titlePrefix,
isLogin,
lastMatch.pathname,
location.search,
matches,
outlet
])
}
export default AuthRoute

8
src/ant-design.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
import * as React from 'react'
import { CustomIconComponentProps } from '@ant-design/icons/es/components/Icon'
declare global {
type IconComponent =
| React.ComponentType<CustomIconComponentProps | React.SVGProps<SVGSVGElement>>
| React.ForwardRefExoticComponent<CustomIconComponentProps>
}

View File

@@ -25,8 +25,8 @@
height: 100%;
}
.background-white {
background-color: white;
.background-origin {
background-color: constants.$origin-color;
}
.center-box {
@@ -107,9 +107,11 @@
}
.flex-horizontal {
display: flex;
flex-direction: row;
}
.flex-vertical {
display: flex;
flex-direction: column;
}

View File

@@ -1,6 +1,7 @@
@use '@/assets/css/constants' as constants;
.hide-scrollbar-mask {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
@@ -14,7 +15,6 @@
.hide-scrollbar-content {
display: inline-block;
width: 100%;
min-width: 900px;
}
}
@@ -52,17 +52,25 @@
.vertical-scrollbar {
padding: 12px 4px;
width: 16px;
height: 100%;
right: 0;
left: 100%;
top: 0;
transform: translateX(-100%);
.box {
width: 8px;
}
}
.horizontal-scrollbar {
padding: 4px 12px;
width: 100%;
height: 16px;
left: 0;
bottom: 0;
top: 100%;
transform: translateY(-100%);
.box {
height: 8px;
}
}
}

View File

@@ -1,19 +1,21 @@
@use "@/assets/css/constants" as constants;
.icons {
display: flex;
gap: 20px;
.icon {
font-size: 8em;
color: white;
color: constants.$origin-color;
}
}
.links {
font-size: 2em;
text-decoration: underline;
color: white;
color: constants.$origin-color;
> * {
color: white;
color: constants.$origin-color;
}
}

View File

@@ -1,8 +1,16 @@
$main-color: #00D4FF;
$origin-color: white;
$main-color: #4E47BB;
$secondary-color: #BAB8E5;
$active-color: #EBECFFD;
$background-color: #F5F5F5;
$font-main-color: #4D4D4D;
$font-secondary-color: #9E9E9E;
$focus-color: #DDDDDD;
$border-color: rgba(204, 204, 204, 0.33);
$url-color: rgba(102, 102, 102, .8);
$url-active-color: #ccc
$url-active-color: #ccc;
$SIZE_ICON_XS: 16px;
$SIZE_ICON_SM: 20px;
$SIZE_ICON_MD: 24px;
$SIZE_ICON_LG: 32px;
$SIZE_ICON_XL: 64px;

View File

@@ -8,7 +8,7 @@
z-index: 1;
width: 100%;
height: 70px;
background-color: white;
background-color: constants.$origin-color;
border: {
bottom: {
width: 1px;
@@ -99,7 +99,7 @@
position: absolute;
width: 100%;
text-align: center;
background-color: white;
background-color: constants.$origin-color;
overflow: hidden;
.item {
@@ -171,7 +171,7 @@
color: constants.$border-color;
style: solid;
};
background-color: white;
background-color: constants.$origin-color;
z-index: 1;
ul {

View File

@@ -1,3 +1,5 @@
@use "@/assets/css/constants" as constants;
.login-background {
display: flex;
height: 100vh;
@@ -24,7 +26,7 @@
.login-box-left-text {
font-size: 3rem;
color: white;
color: constants.$origin-color;
font-weight: bold;
> div:last-child {
@@ -37,7 +39,7 @@
position: relative;
height: 100%;
flex: 3;
background-color: white;
background-color: constants.$origin-color;
}
.login-from-text {

View File

@@ -0,0 +1,247 @@
@use "@/assets/css/constants" as constants;
@use "@/assets/css/mixins" as mixins;
body {
background-color: constants.$background-color;
}
.left-panel {
display: flex;
flex-direction: column;
width: clamp(180px, 20vw, 240px);
background-color: constants.$origin-color;
user-select: none;
transition: all .3s;
white-space: nowrap;
overflow: hidden;
.title {
display: flex;
align-items: center;
font-weight: bold;
padding: 10px 14px;
color: constants.$main-color;
.icon-box {
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
width: 40px;
height: 40px;
font-size: constants.$SIZE_ICON_SM;
border-radius: 8px;
cursor: pointer;
span {
transform: rotateZ(180deg);
transition: all .3s;
}
&:hover {
background-color: constants.$background-color;
}
}
.text {
flex: 1;
font-size: 2em;
text-align: center;
letter-spacing: 0.6em;
transform: translateX(0.3em);
}
}
.content {
display: flex;
min-height: 0;
flex-direction: column;
flex: 1;
.toolsMenu {
min-height: 0;
flex: 1;
}
ul {
> li {
&.item {
position: relative;
margin: 4px 14px;
font-size: 1.4em;
.menu-bt {
border-radius: 8px;
overflow: hidden;
height: 40px;
.icon-box {
display: flex;
justify-content: center;
align-items: center;
padding: 0 10px;
width: 40px;
height: 40px;
font-size: constants.$SIZE_ICON_SM;
cursor: pointer;
}
a {
display: flex;
align-items: center;
height: 100%;
width: 100%;
transition: all 0.2s;
.text {
flex: 1;
padding-left: 8px;
}
&.active {
color: constants.$origin-color;
background-color: constants.$main-color !important;
}
}
}
.submenu {
display: none;
position: fixed;
padding-left: 20px;
z-index: 10000;
.content {
display: flex;
flex-direction: column;
gap: 2px;
padding: 10px 10px;
background-color: constants.$origin-color;
border-radius: 8px;
.item {
border-radius: 8px;
white-space: nowrap;
overflow: hidden;
a {
display: block;
padding: 8px 16px;
transition: all 0.2s;
&.active {
color: constants.$origin-color;
background-color: constants.$main-color !important;
}
}
&:hover a {
background-color: constants.$background-color;
}
}
}
}
&:hover {
.menu-bt {
a {
background-color: constants.$background-color;
}
}
.submenu {
display: block;
animation: 0.3s ease;
@include mixins.unique-keyframes {
0% {
display: block;
transform: translateX(-10px);
opacity: 0;
}
100% {
display: block;
transform: translateX(0);
opacity: 1;
}
}
}
}
}
}
}
}
.separate {
height: 0;
margin: 10px 5px;
border: {
width: 1px;
color: constants.$font-secondary-color;
style: solid;
};
opacity: 0.4;
}
.footer {
display: flex;
align-items: center;
font-weight: bold;
padding: 8px 14px;
color: constants.$main-color;
.icon-box {
display: flex;
justify-content: center;
align-items: center;
margin-left: 4px;
padding: 10px;
width: 32px;
height: 32px;
font-size: constants.$SIZE_ICON_XS;
border: 2px constants.$font-secondary-color solid;
color: constants.$font-secondary-color;
border-radius: 50%;
cursor: pointer;
}
.text {
flex: 1;
padding-left: 8px;
font-size: 1.4em;
color: constants.$font-main-color;
user-select: text;
}
}
&.hide {
width: 68px;
.title {
.icon-box {
span {
transform: rotateZ(360deg);
transition: all .3s;
}
}
.text {
display: none;
}
}
.menu-bt {
.text {
display: none;
}
}
.footer {
.text {
display: none;
}
}
}
}
.right-panel {
flex: 1;
background-color: constants.$background-color;
}

View File

@@ -1 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M958.577476 311.180252c0 15.286148-5.878894 29.522384-16.564257 40.166815L551.623951 741.043556c-1.39272 1.425466-2.577708 2.537799-3.846608 3.449565l-2.905166 2.304486c-11.416004 11.362792-24.848944 16.945951-39.068807 16.945951-14.475689 0-28.010961-5.708002-38.110993-16.056698L79.475588 355.310332c-10.467399-10.613732-16.236799-24.786523-16.236799-39.893592 0-14.772448 5.599532-28.726252 15.753799-39.286772 10.18599-10.497075 24.390503-16.539698 38.95215-16.539698 14.382569 0 28.009937 5.723352 38.366819 16.142655l350.169241 353.968777 359.428116-358.485651c10.30981-10.248412 23.781636-15.909341 37.994336-15.909341 14.889105 0 28.859281 6.05081 39.333844 16.999163C953.126324 282.725176 958.577476 296.556183 958.577476 311.180252z" /></svg>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M958.577476 311.180252c0 15.286148-5.878894 29.522384-16.564257 40.166815L551.623951 741.043556c-1.39272 1.425466-2.577708 2.537799-3.846608 3.449565l-2.905166 2.304486c-11.416004 11.362792-24.848944 16.945951-39.068807 16.945951-14.475689 0-28.010961-5.708002-38.110993-16.056698L79.475588 355.310332c-10.467399-10.613732-16.236799-24.786523-16.236799-39.893592 0-14.772448 5.599532-28.726252 15.753799-39.286772 10.18599-10.497075 24.390503-16.539698 38.95215-16.539698 14.382569 0 28.009937 5.723352 38.366819 16.142655l350.169241 353.968777 359.428116-358.485651c10.30981-10.248412 23.781636-15.909341 37.994336-15.909341 14.889105 0 28.859281 6.05081 39.333844 16.999163C953.126324 282.725176 958.577476 296.556183 958.577476 311.180252z" /></svg>

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 848 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 1028 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M8.533333 192c0-25.6 17.066667-42.666667 34.133334-42.666667h490.666666c21.333333 0 34.133333 17.066667 34.133334 42.666667 0 21.333333-17.066667 42.666667-34.133334 42.666667h-490.666666c-21.333333 0-34.133333-21.333333-34.133334-42.666667z m550.4 213.333333c0 25.6-21.333333 42.666667-46.933333 42.666667h-465.066667c-25.6 0-46.933333-17.066667-46.933333-42.666667 0-21.333333 21.333333-42.666667 46.933333-42.666666h465.066667c25.6 0 46.933333 17.066667 46.933333 42.666666z m-507.733333 256c-25.6 0-46.933333-17.066667-46.933333-42.666666s21.333333-42.666667 46.933333-42.666667h465.066667c25.6 0 46.933333 17.066667 46.933333 42.666667 0 21.333333-21.333333 42.666667-46.933333 42.666666h-465.066667z m507.733333 170.666667c0 25.6-21.333333 42.666667-51.2 42.666667h-452.266666c-29.866667 0-51.2-17.066667-51.2-42.666667s21.333333-42.666667 51.2-42.666667h452.266666c29.866667 0 51.2 17.066667 51.2 42.666667z m140.8-85.333333l221.866667-238.933334-221.866667-238.933333c-17.066667-17.066667-17.066667-51.2 0-68.266667 17.066667-17.066667 46.933333-17.066667 64 0l251.733334 273.066667c17.066667 17.066667 17.066667 51.2 0 68.266667l-251.733334 273.066666c-17.066667 17.066667-46.933333 17.066667-64 0-17.066667-17.066667-17.066667-46.933333 0-68.266666z" /></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1 +1 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" version="1.1" width="32" height="32"><path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z" /></svg>
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z" /></svg>

Before

Width:  |  Height:  |  Size: 687 B

After

Width:  |  Height:  |  Size: 673 B

1
src/assets/svg/home.svg Normal file
View File

@@ -0,0 +1 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M116.707556 631.751111a56.888889 56.888889 0 1 1 113.777777 0V910.222222h539.704889V512a56.888889 56.888889 0 1 1 113.777778 0v398.222222a113.777778 113.777778 0 0 1-113.777778 113.777778H230.485333a113.777778 113.777778 0 0 1-113.777777-113.777778v-278.471111z" /><path d="M98.929778 573.070222a56.888889 56.888889 0 0 1-84.081778-76.629333L412.700444 59.904A113.777778 113.777778 0 0 1 578.019556 56.888889l429.710222 438.044444a56.888889 56.888889 0 0 1-81.237334 79.644445L496.810667 136.533333 98.929778 573.098667z" /></svg>

After

Width:  |  Height:  |  Size: 617 B

View File

@@ -1 +1 @@
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M487.1 425c-1.4-11.2-19-23.1-28.2-31.9-5.1-5-29-23.1-30.4-29.9-1.4-6.6 9.7-21.5 13.3-28.9 5.1-10.7 8.8-23.7 11.3-32.6 18.8-66.1 20.7-156.9-6.2-211.2-10.2-20.6-38.6-49-56.4-62.5-42-31.7-119.6-35.3-170.1-16.6-14.1 5.2-27.8 9.8-40.1 17.1-33.1 19.4-68.3 32.5-78.1 71.6-24.2 10.8-31.5 41.8-30.3 77.8.2 7 4.1 15.8 2.7 22.4-.7 3.3-5.2 7.6-6.1 9.8-11.6 27.7-2.3 64 11.1 83.7 8.1 11.9 21.5 22.4 39.2 25.2.7 10.6 3.3 19.7 8.2 30.4 3.1 6.8 14.7 19 10.4 27.7-2.2 4.4-21 13.8-27.3 17.6C89 407.2 73.7 415 54.2 429c-12.6 9-32.3 10.2-29.2 31.1 2.1 14.1 10.1 31.6 14.7 45.8.7 2 1.4 4.1 2.1 6h422c4.9-15.3 9.7-30.9 14.6-47.2 3.4-11.4 10.2-27.8 8.7-39.7zM205.9 33.7c1.8-.5 3.4.7 4.9 2.4-.2 5.2-5.4 5.1-8.9 6.8-5.4 6.7-13.4 9.8-20 17.2-6.8 7.5-14.4 27.7-23.4 30-4.5 1.1-9.7-.8-13.6-.5-10.4.7-17.7 6-28.3 7.5 13.6-29.9 56.1-54 89.3-63.4zm-104.8 93.6c13.5-14.9 32.1-24.1 54.8-25.9 11.7 29.7-8.4 65-.9 97.6 2.3 9.9 10.2 25.4-2.4 25.7.3-28.3-34.8-46.3-61.3-29.6-1.8-21.5-4.9-51.7 9.8-67.8zm36.7 200.2c-1-4.1-2.7-12.9-2.3-15.1 1.6-8.7 17.1-12.5 11-24.7-11.3-.1-13.8 10.2-24.1 11.3-26.7 2.6-45.6-35.4-44.4-58.4 1-19.5 17.6-38.2 40.1-35.8 16 1.8 21.4 19.2 24.5 34.7 9.2.5 22.5-.4 26.9-7.6-.6-17.5-8.8-31.6-8.2-47.7 1-30.3 17.5-57.6 4.8-87.4 13.6-30.9 53.5-55.3 83.1-70 36.6-18.3 94.9-3.7 129.3 15.8 19.7 11.1 34.4 32.7 48.3 50.7-19.5-5.8-36.1 4.2-33.1 20.3 16.3-14.9 44.2-.2 52.5 16.4 7.9 15.8 7.8 39.3 9 62.8 2.9 57-10.4 115.9-39.1 157.1-7.7 11-14.1 23-24.9 30.6-26 18.2-65.4 34.7-99.2 23.4-44.7-15-65-44.8-89.5-78.8.7 18.7 13.8 34.1 26.8 48.4 11.3 12.5 25 26.6 39.7 32.4-12.3-2.9-31.1-3.8-36.2 7.2-28.6-1.9-55.1-4.8-68.7-24.2-10.6-15.4-21.4-41.4-26.3-61.4zm222 124.1c4.1-3 11.1-2.9 17.4-3.6-5.4-2.7-13-3.7-19.3-2.2-.1-4.2-2-6.8-3.2-10.2 10.6-3.8 35.5-28.5 49.6-20.3 6.7 3.9 9.5 26.2 10.1 37 .4 9-.8 18-4.5 22.8-18.8-.6-35.8-2.8-50.7-7 .9-6.1-1-12.1.6-16.5zm-17.2-20c-16.8.8-26-1.2-38.3-10.8.2-.8 1.4-.5 1.5-1.4 18 8 40.8-3.3 59-4.9-7.9 5.1-14.6 11.6-22.2 17.1zm-12.1 33.2c-1.6-9.4-3.5-12-2.8-20.2 25-16.6 29.7 28.6 2.8 20.2zM226 438.6c-11.6-.7-48.1-14-38.5-23.7 9.4 6.5 27.5 4.9 41.3 7.3.8 4.4-2.8 10.2-2.8 16.4zM57.7 497.1c-4.3-12.7-9.2-25.1-14.8-36.9 30.8-23.8 65.3-48.9 102.2-63.5 2.8-1.1 23.2 25.4 26.2 27.6 16.5 11.7 37 21 56.2 30.2 1.2 8.8 3.9 20.2 8.7 35.5.7 2.3 1.4 4.7 2.2 7.2H57.7zm240.6 5.7h-.8c.3-.2.5-.4.8-.5v.5zm7.5-5.7c2.1-1.4 4.3-2.8 6.4-4.3 1.1 1.4 2.2 2.8 3.2 4.3h-9.6zm15.1-24.7c-10.8 7.3-20.6 18.3-33.3 25.2-6 3.3-27 11.7-33.4 10.2-3.6-.8-3.9-5.3-5.4-9.5-3.1-9-10.1-23.4-10.8-37-.8-17.2-2.5-46 16-42.4 14.9 2.9 32.3 9.7 43.9 16.1 7.1 3.9 11.1 8.6 21.9 9.5-.1 1.4-.1 2.8-.2 4.3-5.9 3.9-15.3 3.8-21.8 7.1 9.5.4 17 2.7 23.5 5.9-.1 3.4-.3 7-.4 10.6zm53.4 24.7h-14c-.1-3.2-2.8-5.8-6.1-5.8s-5.9 2.6-6.1 5.8h-17.4c-2.8-4.4-5.7-8.6-8.9-12.5 2.1-2.2 4-4.7 6-6.9 9 3.7 14.8-4.9 21.7-4.2 7.9.8 14.2 11.7 25.4 11l-.6 12.6zm8.7 0c.2-4 .4-7.8.6-11.5 15.6-7.3 29 1.3 35.7 11.5H383zm83.4-37c-2.3 11.2-5.8 24-9.9 37.1-.2-.1-.4-.1-.6-.1H428c.6-1.1 1.2-2.2 1.9-3.3-2.6-6.1-9-8.7-10.9-15.5 12.1-22.7 6.5-93.4-24.2-78.5 4.3-6.3 15.6-11.5 20.8-19.3 13 10.4 20.8 20.3 33.2 31.4 6.8 6 20 13.3 21.4 23.1.8 5.5-2.6 18.9-3.8 25.1zM222.2 130.5c5.4-14.9 27.2-34.7 45-32 7.7 1.2 18 8.2 12.2 17.7-30.2-7-45.2 12.6-54.4 33.1-8.1-2-4.9-13.1-2.8-18.8zm184.1 63.1c8.2-3.6 22.4-.7 29.6-5.3-4.2-11.5-10.3-21.4-9.3-37.7.5 0 1 0 1.4.1 6.8 14.2 12.7 29.2 21.4 41.7-5.7 13.5-43.6 25.4-43.1 1.2zm20.4-43zm-117.2 45.7c-6.8-10.9-19-32.5-14.5-45.3 6.5 11.9 8.6 24.4 17.8 33.3 4.1 4 12.2 9 8.2 20.2-.9 2.7-7.8 8.6-11.7 9.7-14.4 4.3-47.9.9-36.6-17.1 11.9.7 27.9 7.8 36.8-.8zm27.3 70c3.8 6.6 1.4 18.7 12.1 20.6 20.2 3.4 43.6-12.3 58.1-17.8 9-15.2-.8-20.7-8.9-30.5-16.6-20-38.8-44.8-38-74.7 6.7-4.9 7.3 7.4 8.2 9.7 8.7 20.3 30.4 46.2 46.3 63.5 3.9 4.3 10.3 8.4 11 11.2 2.1 8.2-5.4 18-4.5 23.5-21.7 13.9-45.8 29.1-81.4 25.6-7.4-6.7-10.3-21.4-2.9-31.1zm-201.3-9.2c-6.8-3.9-8.4-21-16.4-21.4-11.4-.7-9.3 22.2-9.3 35.5-7.8-7.1-9.2-29.1-3.5-40.3-6.6-3.2-9.5 3.6-13.1 5.9 4.7-34.1 49.8-15.8 42.3 20.3zm299.6 28.8c-10.1 19.2-24.4 40.4-54 41-.6-6.2-1.1-15.6 0-19.4 22.7-2.2 36.6-13.7 54-21.6zm-141.9 12.4c18.9 9.9 53.6 11 79.3 10.2 1.4 5.6 1.3 12.6 1.4 19.4-33 1.8-72-6.4-80.7-29.6zm92.2 46.7c-1.7 4.3-5.3 9.3-9.8 11.1-12.1 4.9-45.6 8.7-62.4-.3-10.7-5.7-17.5-18.5-23.4-26-2.8-3.6-16.9-12.9-.2-12.9 13.1 32.7 58 29 95.8 28.1z"/></svg>
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M487.1 425c-1.4-11.2-19-23.1-28.2-31.9-5.1-5-29-23.1-30.4-29.9-1.4-6.6 9.7-21.5 13.3-28.9 5.1-10.7 8.8-23.7 11.3-32.6 18.8-66.1 20.7-156.9-6.2-211.2-10.2-20.6-38.6-49-56.4-62.5-42-31.7-119.6-35.3-170.1-16.6-14.1 5.2-27.8 9.8-40.1 17.1-33.1 19.4-68.3 32.5-78.1 71.6-24.2 10.8-31.5 41.8-30.3 77.8.2 7 4.1 15.8 2.7 22.4-.7 3.3-5.2 7.6-6.1 9.8-11.6 27.7-2.3 64 11.1 83.7 8.1 11.9 21.5 22.4 39.2 25.2.7 10.6 3.3 19.7 8.2 30.4 3.1 6.8 14.7 19 10.4 27.7-2.2 4.4-21 13.8-27.3 17.6C89 407.2 73.7 415 54.2 429c-12.6 9-32.3 10.2-29.2 31.1 2.1 14.1 10.1 31.6 14.7 45.8.7 2 1.4 4.1 2.1 6h422c4.9-15.3 9.7-30.9 14.6-47.2 3.4-11.4 10.2-27.8 8.7-39.7zM205.9 33.7c1.8-.5 3.4.7 4.9 2.4-.2 5.2-5.4 5.1-8.9 6.8-5.4 6.7-13.4 9.8-20 17.2-6.8 7.5-14.4 27.7-23.4 30-4.5 1.1-9.7-.8-13.6-.5-10.4.7-17.7 6-28.3 7.5 13.6-29.9 56.1-54 89.3-63.4zm-104.8 93.6c13.5-14.9 32.1-24.1 54.8-25.9 11.7 29.7-8.4 65-.9 97.6 2.3 9.9 10.2 25.4-2.4 25.7.3-28.3-34.8-46.3-61.3-29.6-1.8-21.5-4.9-51.7 9.8-67.8zm36.7 200.2c-1-4.1-2.7-12.9-2.3-15.1 1.6-8.7 17.1-12.5 11-24.7-11.3-.1-13.8 10.2-24.1 11.3-26.7 2.6-45.6-35.4-44.4-58.4 1-19.5 17.6-38.2 40.1-35.8 16 1.8 21.4 19.2 24.5 34.7 9.2.5 22.5-.4 26.9-7.6-.6-17.5-8.8-31.6-8.2-47.7 1-30.3 17.5-57.6 4.8-87.4 13.6-30.9 53.5-55.3 83.1-70 36.6-18.3 94.9-3.7 129.3 15.8 19.7 11.1 34.4 32.7 48.3 50.7-19.5-5.8-36.1 4.2-33.1 20.3 16.3-14.9 44.2-.2 52.5 16.4 7.9 15.8 7.8 39.3 9 62.8 2.9 57-10.4 115.9-39.1 157.1-7.7 11-14.1 23-24.9 30.6-26 18.2-65.4 34.7-99.2 23.4-44.7-15-65-44.8-89.5-78.8.7 18.7 13.8 34.1 26.8 48.4 11.3 12.5 25 26.6 39.7 32.4-12.3-2.9-31.1-3.8-36.2 7.2-28.6-1.9-55.1-4.8-68.7-24.2-10.6-15.4-21.4-41.4-26.3-61.4zm222 124.1c4.1-3 11.1-2.9 17.4-3.6-5.4-2.7-13-3.7-19.3-2.2-.1-4.2-2-6.8-3.2-10.2 10.6-3.8 35.5-28.5 49.6-20.3 6.7 3.9 9.5 26.2 10.1 37 .4 9-.8 18-4.5 22.8-18.8-.6-35.8-2.8-50.7-7 .9-6.1-1-12.1.6-16.5zm-17.2-20c-16.8.8-26-1.2-38.3-10.8.2-.8 1.4-.5 1.5-1.4 18 8 40.8-3.3 59-4.9-7.9 5.1-14.6 11.6-22.2 17.1zm-12.1 33.2c-1.6-9.4-3.5-12-2.8-20.2 25-16.6 29.7 28.6 2.8 20.2zM226 438.6c-11.6-.7-48.1-14-38.5-23.7 9.4 6.5 27.5 4.9 41.3 7.3.8 4.4-2.8 10.2-2.8 16.4zM57.7 497.1c-4.3-12.7-9.2-25.1-14.8-36.9 30.8-23.8 65.3-48.9 102.2-63.5 2.8-1.1 23.2 25.4 26.2 27.6 16.5 11.7 37 21 56.2 30.2 1.2 8.8 3.9 20.2 8.7 35.5.7 2.3 1.4 4.7 2.2 7.2H57.7zm240.6 5.7h-.8c.3-.2.5-.4.8-.5v.5zm7.5-5.7c2.1-1.4 4.3-2.8 6.4-4.3 1.1 1.4 2.2 2.8 3.2 4.3h-9.6zm15.1-24.7c-10.8 7.3-20.6 18.3-33.3 25.2-6 3.3-27 11.7-33.4 10.2-3.6-.8-3.9-5.3-5.4-9.5-3.1-9-10.1-23.4-10.8-37-.8-17.2-2.5-46 16-42.4 14.9 2.9 32.3 9.7 43.9 16.1 7.1 3.9 11.1 8.6 21.9 9.5-.1 1.4-.1 2.8-.2 4.3-5.9 3.9-15.3 3.8-21.8 7.1 9.5.4 17 2.7 23.5 5.9-.1 3.4-.3 7-.4 10.6zm53.4 24.7h-14c-.1-3.2-2.8-5.8-6.1-5.8s-5.9 2.6-6.1 5.8h-17.4c-2.8-4.4-5.7-8.6-8.9-12.5 2.1-2.2 4-4.7 6-6.9 9 3.7 14.8-4.9 21.7-4.2 7.9.8 14.2 11.7 25.4 11l-.6 12.6zm8.7 0c.2-4 .4-7.8.6-11.5 15.6-7.3 29 1.3 35.7 11.5H383zm83.4-37c-2.3 11.2-5.8 24-9.9 37.1-.2-.1-.4-.1-.6-.1H428c.6-1.1 1.2-2.2 1.9-3.3-2.6-6.1-9-8.7-10.9-15.5 12.1-22.7 6.5-93.4-24.2-78.5 4.3-6.3 15.6-11.5 20.8-19.3 13 10.4 20.8 20.3 33.2 31.4 6.8 6 20 13.3 21.4 23.1.8 5.5-2.6 18.9-3.8 25.1zM222.2 130.5c5.4-14.9 27.2-34.7 45-32 7.7 1.2 18 8.2 12.2 17.7-30.2-7-45.2 12.6-54.4 33.1-8.1-2-4.9-13.1-2.8-18.8zm184.1 63.1c8.2-3.6 22.4-.7 29.6-5.3-4.2-11.5-10.3-21.4-9.3-37.7.5 0 1 0 1.4.1 6.8 14.2 12.7 29.2 21.4 41.7-5.7 13.5-43.6 25.4-43.1 1.2zm20.4-43zm-117.2 45.7c-6.8-10.9-19-32.5-14.5-45.3 6.5 11.9 8.6 24.4 17.8 33.3 4.1 4 12.2 9 8.2 20.2-.9 2.7-7.8 8.6-11.7 9.7-14.4 4.3-47.9.9-36.6-17.1 11.9.7 27.9 7.8 36.8-.8zm27.3 70c3.8 6.6 1.4 18.7 12.1 20.6 20.2 3.4 43.6-12.3 58.1-17.8 9-15.2-.8-20.7-8.9-30.5-16.6-20-38.8-44.8-38-74.7 6.7-4.9 7.3 7.4 8.2 9.7 8.7 20.3 30.4 46.2 46.3 63.5 3.9 4.3 10.3 8.4 11 11.2 2.1 8.2-5.4 18-4.5 23.5-21.7 13.9-45.8 29.1-81.4 25.6-7.4-6.7-10.3-21.4-2.9-31.1zm-201.3-9.2c-6.8-3.9-8.4-21-16.4-21.4-11.4-.7-9.3 22.2-9.3 35.5-7.8-7.1-9.2-29.1-3.5-40.3-6.6-3.2-9.5 3.6-13.1 5.9 4.7-34.1 49.8-15.8 42.3 20.3zm299.6 28.8c-10.1 19.2-24.4 40.4-54 41-.6-6.2-1.1-15.6 0-19.4 22.7-2.2 36.6-13.7 54-21.6zm-141.9 12.4c18.9 9.9 53.6 11 79.3 10.2 1.4 5.6 1.3 12.6 1.4 19.4-33 1.8-72-6.4-80.7-29.6zm92.2 46.7c-1.7 4.3-5.3 9.3-9.8 11.1-12.1 4.9-45.6 8.7-62.4-.3-10.7-5.7-17.5-18.5-23.4-26-2.8-3.6-16.9-12.9-.2-12.9 13.1 32.7 58 29 95.8 28.1z" /></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -1 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3 0.1 19.9-16 36-35.9 36z" /></svg>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3 0.1 19.9-16 36-35.9 36z" /></svg>

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 425 B

View File

@@ -1 +1 @@
<svg id="logo" xmlns="http://www.w3.org/2000/svg" width="911.75" height="898.91" viewBox="0 0 911.75 898.91"><path d="M828.52,831.72c-216,259.7-774.46,115.85-765.17-319.5-19-292.76,370.82-602.09,704.81-367.11,44.53,48.2,81.39,32.52,121.28,46.67,28.28,14.3,21.36,42.61,52.36,142.6,31.6,101.09,60.92,133.92-17.45,182.4-28,11.34-46.6-2.34-117.82-25.31-92-29.65-109-22.33-132.66-48-27.26-29.62-14.42-50.13-41.31-80.13C507.2,259.81,336.39,351,290.74,489.72c-7,26.59-20.37,92.15,8.72,162.38,25.72,62.11,71.11,95.23,99.93,115.7C554.85,878.2,781.33,840.41,828.52,831.72Zm50.11-584.95c-15.4,5.93.73,47,16,40.82C910.07,281.66,893.94,240.63,878.63,246.77Zm-152,180.06c-4.34,15.92,35.73,26.64,39.92,10.68C770.9,421.58,730.83,410.86,726.63,426.83Z" transform="translate(-62.68 -62.16)"/></svg>
<svg viewBox="0 0 911.75 898.91" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M828.52,831.72c-216,259.7-774.46,115.85-765.17-319.5-19-292.76,370.82-602.09,704.81-367.11,44.53,48.2,81.39,32.52,121.28,46.67,28.28,14.3,21.36,42.61,52.36,142.6,31.6,101.09,60.92,133.92-17.45,182.4-28,11.34-46.6-2.34-117.82-25.31-92-29.65-109-22.33-132.66-48-27.26-29.62-14.42-50.13-41.31-80.13C507.2,259.81,336.39,351,290.74,489.72c-7,26.59-20.37,92.15,8.72,162.38,25.72,62.11,71.11,95.23,99.93,115.7C554.85,878.2,781.33,840.41,828.52,831.72Zm50.11-584.95c-15.4,5.93.73,47,16,40.82C910.07,281.66,893.94,240.63,878.63,246.77Zm-152,180.06c-4.34,15.92,35.73,26.64,39.92,10.68C770.9,421.58,730.83,410.86,726.63,426.83Z" transform="translate(-62.68 -62.16)"/></svg>

Before

Width:  |  Height:  |  Size: 780 B

After

Width:  |  Height:  |  Size: 762 B

View File

@@ -1 +1 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><path d="M950.784 768v72.704q0 15.36-10.24 25.6t-26.624 11.264H110.08q-15.36 0-25.6-11.264t-11.264-25.6V768q0-15.36 11.264-25.6t25.6-11.264h803.84q15.36 0 26.624 11.264t10.24 25.6z m0-292.864v73.728q0 14.336-10.24 25.6t-26.624 10.24H110.08q-15.36 0-25.6-10.24t-11.264-25.6v-73.728q0-14.336 11.264-25.6t25.6-10.24h803.84q15.36 0 26.624 10.24t10.24 25.6z m0-291.84V256q0 14.336-10.24 25.6t-26.624 11.264H110.08q-15.36 0-25.6-11.264T73.216 256V183.296q0-15.36 11.264-26.624t25.6-10.24h803.84q15.36 0 26.624 10.24t10.24 26.624z" /></svg>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M950.784 768v72.704q0 15.36-10.24 25.6t-26.624 11.264H110.08q-15.36 0-25.6-11.264t-11.264-25.6V768q0-15.36 11.264-25.6t25.6-11.264h803.84q15.36 0 26.624 11.264t10.24 25.6z m0-292.864v73.728q0 14.336-10.24 25.6t-26.624 10.24H110.08q-15.36 0-25.6-10.24t-11.264-25.6v-73.728q0-14.336 11.264-25.6t25.6-10.24h803.84q15.36 0 26.624 10.24t10.24 25.6z m0-291.84V256q0 14.336-10.24 25.6t-26.624 11.264H110.08q-15.36 0-25.6-11.264T73.216 256V183.296q0-15.36 11.264-26.624t25.6-10.24h803.84q15.36 0 26.624 10.24t10.24 26.624z" /></svg>

Before

Width:  |  Height:  |  Size: 649 B

After

Width:  |  Height:  |  Size: 620 B

1
src/assets/svg/user.svg Normal file
View File

@@ -0,0 +1 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M707.797333 304.426667c0-114.218667-93.354667-206.805333-208.512-206.805334-115.157333 0-208.512 92.586667-208.512 206.805334 0 114.218667 93.354667 206.805333 208.512 206.805333 115.2 0 208.512-92.586667 208.512-206.805333m292.053334 656c1.365333 4.437333 2.304 9.088 2.304 13.994666 0 26.88-21.930667 48.725333-49.024 48.725334a48.853333 48.853333 0 0 1-48.981334-48.725334v-0.170666h-1.28c-12.757333-203.904-182.741333-365.482667-391.168-365.482667-208.469333 0-378.453333 161.578667-391.253333 365.482667h-1.109333v0.170666c0 26.88-21.930667 48.725333-48.981334 48.725334A48.853333 48.853333 0 0 1 21.333333 974.421333c0-4.608 0.853333-8.96 2.048-13.226666 14.250667-185.045333 131.882667-341.248 296.149334-410.88C243.2 494.976 193.28 405.589333 193.28 304.426667 193.28 136.32 330.453333 0 499.626667 0c169.216 0 306.346667 136.32 306.346666 304.426667a302.933333 302.933333 0 0 1-118.186666 239.914666c172.202667 66.005333 297.088 225.536 312.064 416.085334" /></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -11,7 +11,7 @@ const FitCenter: React.FC<FitCenterProps> = (props) => {
return (
<>
<div
className={`fit-center${className ? ' ' + className : ''}${
className={`fit-center${className ? ` ${className}` : ''}${
vertical ? ' flex-vertical' : ' flex-horizontal'
}`}
{..._props}

View File

@@ -12,7 +12,7 @@ const FitFullScreen = forwardRef<HTMLDivElement, FitFullscreenProps>((props, ref
return (
<>
<div
className={`fit-fullscreen${className ? ' ' + className : ''}`}
className={`fit-fullscreen${className ? ` ${className}` : ''}`}
style={{
zIndex,
backgroundColor,

View File

@@ -10,6 +10,10 @@ interface HideScrollbarProps
isHiddenVerticalScrollbarWhenFull?: boolean
isShowHorizontalScrollbar?: boolean
isHiddenHorizontalScrollbarWhenFull?: boolean
minWidth?: string | number
minHeight?: string | number
scrollbarWidth?: string | number
animationTransitionTime?: number
}
export interface HideScrollbarElement {
@@ -42,6 +46,7 @@ export interface HideScrollbarElement {
listener: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions
): void
refreshLayout(): void
}
const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>((props, ref) => {
@@ -125,6 +130,9 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>((prop
options?: boolean | EventListenerOptions
): void {
rootRef.current?.removeEventListener(type, listener, options)
},
refreshLayout(): void {
refreshLayout()
}
}
},
@@ -138,6 +146,7 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>((prop
const lastScrollbarClickPositionRef = useRef({ x: -1, y: -1 })
const lastScrollbarTouchPositionRef = useRef({ x: -1, y: -1 })
const lastTouchPositionRef = useRef({ x: -1, y: -1 })
const [refreshTime, setRefreshTime] = useState(0)
const [verticalScrollbarWidth, setVerticalScrollbarWidth] = useState(0)
const [verticalScrollbarLength, setVerticalScrollbarLength] = useState(100)
const [verticalScrollbarPosition, setVerticalScrollbarPosition] = useState(0)
@@ -157,6 +166,10 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>((prop
isHiddenVerticalScrollbarWhenFull,
isShowHorizontalScrollbar,
isHiddenHorizontalScrollbarWhenFull,
minWidth,
minHeight,
scrollbarWidth,
animationTransitionTime,
..._props
} = props
@@ -363,24 +376,32 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>((prop
)
}
const refreshLayout = () => {
setRefreshTime(Date.now())
}
useEffect(() => {
const windowResizeListener = () => {
setVerticalScrollbarWidth(
(rootRef.current?.offsetWidth ?? 0) - (rootRef.current?.clientWidth ?? 0)
)
setHorizontalScrollbarWidth(
(rootRef.current?.offsetHeight ?? 0) - (rootRef.current?.clientHeight ?? 0)
)
rootRef.current &&
setVerticalScrollbarLength(
(rootRef.current.clientHeight / (contentRef.current?.clientHeight ?? 0)) * 100
setTimeout(() => {
setVerticalScrollbarWidth(
(rootRef.current?.offsetWidth ?? 0) - (rootRef.current?.clientWidth ?? 0)
)
setHorizontalScrollbarWidth(
(rootRef.current?.offsetHeight ?? 0) - (rootRef.current?.clientHeight ?? 0)
)
rootRef.current &&
setHorizontalScrollbarLength(
(rootRef.current.clientWidth / (contentRef.current?.clientWidth ?? 0)) * 100
)
rootRef.current &&
setVerticalScrollbarLength(
(rootRef.current.clientHeight / (contentRef.current?.clientHeight ?? 0)) *
100
)
rootRef.current &&
setHorizontalScrollbarLength(
(rootRef.current.clientWidth / (contentRef.current?.clientWidth ?? 0)) * 100
)
refreshLayout()
}, animationTransitionTime)
return windowResizeListener
}
@@ -425,6 +446,7 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>((prop
window.removeEventListener('resize', windowResizeListener)
}
}, [
animationTransitionTime,
horizontalScrollbarLength,
isPreventAnyScroll,
isPreventHorizontalScroll,
@@ -457,8 +479,8 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>((prop
className={'hide-scrollbar-selection'}
tabIndex={0}
style={{
width: `calc(100vw + ${verticalScrollbarWidth}px)`,
height: `calc(100vh + ${horizontalScrollbarWidth}px)`,
width: `calc(${maskRef.current?.clientWidth}px + ${verticalScrollbarWidth}px)`,
height: `calc(${maskRef.current?.clientHeight}px + ${horizontalScrollbarWidth}px)`,
touchAction: isPreventAnyScroll ? 'none' : '',
msTouchAction: isPreventAnyScroll ? 'none' : ''
}}
@@ -469,7 +491,12 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>((prop
onTouchMove={isPreventAnyScroll ? handleDefaultTouchmove : undefined}
onScroll={handleDefaultScroll}
>
<div className={'hide-scrollbar-content'} ref={contentRef}>
<div
className={'hide-scrollbar-content'}
ref={contentRef}
style={{ minWidth, minHeight }}
data-refresh={refreshTime}
>
{props.children}
</div>
</div>
@@ -477,11 +504,18 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>((prop
hidden={
!isShowVerticalScrollbar ||
((isHiddenVerticalScrollbarWhenFull ?? true) &&
verticalScrollbarLength === 100)
verticalScrollbarLength >= 100)
}
className={'scrollbar vertical-scrollbar'}
style={{
height: maskRef.current ? maskRef.current?.clientHeight - 1 : undefined,
top: maskRef.current?.clientTop,
left: maskRef.current
? maskRef.current?.clientLeft + maskRef.current?.clientWidth - 1
: undefined
}}
>
<div className={'box'}>
<div className={'box'} style={{ width: scrollbarWidth }}>
<div
className={'block'}
style={{
@@ -507,11 +541,18 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>((prop
hidden={
!isShowHorizontalScrollbar ||
((isHiddenHorizontalScrollbarWhenFull ?? true) &&
horizontalScrollbarLength === 100)
horizontalScrollbarLength >= 100)
}
className={'scrollbar horizontal-scrollbar'}
style={{
width: maskRef.current ? maskRef.current?.clientWidth - 1 : undefined,
left: maskRef.current?.clientLeft,
top: maskRef.current
? maskRef.current?.clientTop + maskRef.current?.clientHeight - 1
: undefined
}}
>
<div className={'box'}>
<div className={'box'} style={{ height: scrollbarWidth }}>
<div
className={'block'}
style={{

View File

@@ -24,7 +24,7 @@ const Indicator: React.FC<IndicatorProps> = (props) => {
return (
<li
key={index}
className={'item center-box' + (index === current ? ' active' : '')}
className={`item center-box${index === current ? ' active' : ''}`}
onClick={handleClick(index)}
>
<div className={'dot'} />

View File

@@ -1,8 +1,8 @@
import React from 'react'
import FitCenter from '@/components/common/FitCenter.tsx'
import FitCenter from '@/components/common/FitCenter'
import Icon from '@ant-design/icons'
import '@/assets/css/components/home/footer.scss'
import FitFullScreen from '@/components/common/FitFullScreen.tsx'
import FitFullScreen from '@/components/common/FitFullScreen'
import { NavLink } from 'react-router-dom'
const Footer: React.FC = () => {

View File

@@ -1,5 +1,5 @@
import React from 'react'
import FitCenter from '@/components/common/FitCenter.tsx'
import FitCenter from '@/components/common/FitCenter'
const OxygenToolbox: React.FC = () => {
return (

View File

@@ -1,7 +1,7 @@
import React from 'react'
import Icon from '@ant-design/icons'
import '@/assets/css/components/home/slogan.scss'
import FitCenter from '@/components/common/FitCenter.tsx'
import FitCenter from '@/components/common/FitCenter'
interface SloganProps {
onClickScrollDown: (event: React.MouseEvent) => void

View File

@@ -1,4 +1,5 @@
/// <reference types="vite/client" />
/// <reference types="./ant-design" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
@@ -9,10 +10,28 @@ interface ImportMeta {
readonly env: ImportMetaEnv
}
type ToolsJsonObject = {
path: string
id: string
component?: React.ComponentType
name?: string
titlePrefix?: string
title?: string
titlePostfix?: string
icon?: IconComponent
menu?: boolean
auth?: boolean
children?: ToolsJsonObject[]
}
type RouteHandle = {
name?: string
menu?: boolean
auth?: boolean
titlePrefix?: string
title?: string
titlePostfix?: string
icon?: IconComponent
}
type _Response<T> = {

View File

@@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client'
import zh_CN from 'antd/locale/zh_CN'
import '@/assets/css/base.scss'
import '@/assets/css/common.scss'
import App from './App.tsx'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>

View File

@@ -1,5 +1,5 @@
import React from 'react'
import '@/assets/css/pages/header.scss'
import '@/assets/css/pages/home-framework.scss'
import router from '@/router'
import LoadingMask from '@/components/common/LoadingMask'
import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar'
@@ -7,7 +7,7 @@ import Icon from '@ant-design/icons'
import { COLOR_FONT_SECONDARY } from '@/constants/Common.constants.ts'
import { NavLink } from 'react-router-dom'
export const MainFrameworkContext = createContext<{
export const HomeFrameworkContext = createContext<{
navbarHiddenState: {
navbarHidden: boolean
setNavbarHidden: (newValue: boolean) => void
@@ -53,7 +53,7 @@ export const MainFrameworkContext = createContext<{
hideScrollbarRef: createRef()
})
const MainFramework: React.FC = () => {
const HomeFramework: React.FC = () => {
const routeId = useMatches()[1].id
const routeChildren = router.routes[0].children?.find((value) => value.id === routeId)?.children
@@ -81,10 +81,11 @@ const MainFramework: React.FC = () => {
ref={hideScrollbarRef}
isPreventVerticalScroll={preventScroll}
isShowHorizontalScrollbar={true}
minWidth={'900px'}
>
<div className={'body'}>
<div>
<header className={'nav' + (navbarHidden ? ' hide' : '')}>
<header className={`nav${navbarHidden ? ' hide' : ''}`}>
<a className={'logo'} href={'https://fatweb.top'}>
<span className={'title'}>FatWeb</span>
</a>
@@ -115,11 +116,9 @@ const MainFramework: React.FC = () => {
key={subRoute.id}
>
<NavLink
to={
(route.path ?? '') +
'/' +
(subRoute.path ?? '')
}
to={`${route.path ?? ''}/${
subRoute.path ?? ''
}`}
className={({
isActive,
isPending
@@ -153,9 +152,9 @@ const MainFramework: React.FC = () => {
})}
</ul>
<div
className={
'dropdown-menu-button' + (showDropdownMenu ? ' active' : '')
}
className={`dropdown-menu-button${
showDropdownMenu ? ' active' : ''
}`}
>
<Icon
component={IconFatwebMenu}
@@ -165,9 +164,7 @@ const MainFramework: React.FC = () => {
</div>
</nav>
</header>
<div
className={'dropdown-menu-content' + (showDropdownMenu ? ' show' : '')}
>
<div className={`dropdown-menu-content${showDropdownMenu ? ' show' : ''}`}>
<ul>
{routeChildren?.map((route) => {
return (
@@ -187,7 +184,7 @@ const MainFramework: React.FC = () => {
</div>
</div>
<MainFrameworkContext.Provider
<HomeFrameworkContext.Provider
value={{
navbarHiddenState: { navbarHidden, setNavbarHidden },
preventScrollState: { preventScroll, setPreventScroll },
@@ -215,11 +212,11 @@ const MainFramework: React.FC = () => {
>
<Outlet />
</Suspense>
</MainFrameworkContext.Provider>
</HomeFrameworkContext.Provider>
</div>
</HideScrollbar>
</>
)
}
export default MainFramework
export default HomeFramework

View File

@@ -12,6 +12,7 @@ import '@/assets/css/pages/login.scss'
const Login: React.FC = () => {
const [messageApi, contextHolder] = message.useMessage()
const navigate = useNavigate()
const [searchParams] = useSearchParams()
const [isLoggingIn, setIsLoggingIn] = useState(false)
const onFinish = (values: LoginForm) => {
@@ -25,7 +26,11 @@ const Login: React.FC = () => {
setToken(data?.token ?? '')
void messageApi.success('登录成功')
setTimeout(() => {
navigate('/')
if (searchParams.has('redirect')) {
navigate(searchParams.get('redirect') ?? '/')
} else {
navigate('/')
}
}, 1500)
break
case SYSTEM_USERNAME_NOT_FOUND:

View File

@@ -0,0 +1,197 @@
import React from 'react'
import FitFullScreen from '@/components/common/FitFullScreen'
import '@/assets/css/pages/tools-framework.scss'
import Icon from '@ant-design/icons'
import { toolsJsonObjects } from '@/router/tools.tsx'
import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar.tsx'
import { getLocalStorage, setLocalStorage } from '@/utils/common.ts'
const ToolsFramework: React.FC = () => {
const hideScrollbarRef = useRef<HideScrollbarElement>(null)
const [submenuTop, setSubmenuTop] = useState(0)
const [submenuLeft, setSubmenuLeft] = useState(0)
const [hideSidebar, setHideSidebar] = useState(getLocalStorage('hideSidebar') === 'false')
const switchSidebar = () => {
setHideSidebar(!hideSidebar)
setLocalStorage('hideSidebar', hideSidebar ? 'true' : 'false')
setTimeout(() => {
hideScrollbarRef.current?.refreshLayout()
}, 300)
}
const showSubmenu = (e: React.MouseEvent) => {
const parentElement = e.currentTarget.parentElement
if (parentElement && parentElement.childElementCount === 2) {
const parentClientRect = parentElement.getBoundingClientRect()
if (parentClientRect.top <= screen.height / 2) {
setSubmenuTop(parentClientRect.top)
} else {
setSubmenuTop(
parentClientRect.top -
(parentElement.lastElementChild?.clientHeight ?? 0) +
e.currentTarget.clientHeight
)
}
setSubmenuLeft(parentClientRect.right)
}
}
return (
<>
<FitFullScreen className={'flex-horizontal'}>
<div className={`left-panel${hideSidebar ? ' hide' : ''}`}>
<div className={'title'}>
<span className={'icon-box'} onClick={switchSidebar}>
<Icon component={IconFatwebExpand} />
</span>
<span className={'text'}></span>
</div>
<div style={{ marginTop: '0' }} className={'separate'} />
<div className={'content'}>
<ul>
<li className={'item'}>
<div className={'menu-bt'}>
<NavLink
to={''}
end
className={({ isActive, isPending }) =>
isPending ? 'pending' : isActive ? 'active' : ''
}
>
<div className={'icon-box'}>
{toolsJsonObjects[0].icon ? (
<Icon
className={'icon'}
component={toolsJsonObjects[0].icon}
/>
) : undefined}
</div>
<span className={'text'}>{toolsJsonObjects[0].name}</span>
</NavLink>
</div>
</li>
<li className={'item'}>
<div className={'menu-bt'}>
<NavLink
to={'all'}
className={({ isActive, isPending }) =>
isPending ? ' pending' : isActive ? ' active' : ''
}
>
<div className={'icon-box'}>
{toolsJsonObjects[1].icon ? (
<Icon
className={'icon'}
component={toolsJsonObjects[1].icon}
/>
) : undefined}
</div>
<span className={'text'}>{toolsJsonObjects[1].name}</span>
</NavLink>
</div>
</li>
<li>
<div className={'separate'} style={{ marginBottom: 0 }} />
</li>
</ul>
<div className={'toolsMenu'}>
<HideScrollbar
isShowVerticalScrollbar={true}
scrollbarWidth={2}
animationTransitionTime={300}
ref={hideScrollbarRef}
>
<ul>
{toolsJsonObjects.map((tool) => {
return tool.menu &&
tool.id !== 'tools' &&
tool.id !== 'tools-all' ? (
<li className={'item'} key={tool.id}>
<div
className={'menu-bt'}
onMouseEnter={showSubmenu}
>
<NavLink
to={tool.path}
className={({ isActive, isPending }) =>
isPending
? 'pending'
: isActive
? 'active'
: ''
}
>
<div className={'icon-box'}>
{tool.icon ? (
<Icon
className={'icon'}
component={tool.icon}
/>
) : undefined}
</div>
<span className={'text'}>{tool.name}</span>
</NavLink>
</div>
{tool.children ? (
<ul
className={'submenu'}
style={{
top: submenuTop,
left: submenuLeft
}}
>
<div className={'content'}>
{tool.children.map((subTool) => {
return subTool.menu ? (
<li
className={'item'}
key={subTool.id}
>
<NavLink
to={`${tool.path}/${subTool.path}`}
className={({
isActive,
isPending
}) =>
isPending
? 'pending'
: isActive
? 'active'
: ''
}
>
<span
className={'text'}
>
{subTool.name}
</span>
</NavLink>
</li>
) : undefined
})}
</div>
</ul>
) : undefined}
</li>
) : undefined
})}
</ul>
</HideScrollbar>
</div>
</div>
<div className={'separate'} style={{ marginTop: 0, marginBottom: 0 }} />
<div className={'footer'}>
<span className={'icon-box'}>
<Icon component={IconFatwebUser} />
</span>
<span className={'text'}></span>
</div>
</div>
<div className={'right-panel'}></div>
</FitFullScreen>
</>
)
}
export default ToolsFramework

View File

@@ -1,7 +1,7 @@
import React from 'react'
import '@/assets/css/components/home/home.scss'
import FitFullScreen from '@/components/common/FitFullScreen'
import { MainFrameworkContext } from '@/pages/MainFramework'
import { HomeFrameworkContext } from '@/pages/HomeFramework'
import Slogan from '@/components/home/Slogan'
import OxygenToolbox from '@/components/home/OxygenToolbox'
import Indicator from '@/components/common/Indicator'
@@ -13,7 +13,7 @@ const Home: React.FC = () => {
navbarHiddenState: { navbarHidden, setNavbarHidden },
showDropdownMenuState: { setShowDropdownMenu },
preventScrollState: { setPreventScroll }
} = useContext(MainFrameworkContext)
} = useContext(HomeFrameworkContext)
const fitFullScreenRef = useRef<HTMLDivElement>(null)
const scrollTimeout = useRef(0)

7
src/pages/tools/All.tsx Normal file
View File

@@ -0,0 +1,7 @@
import React from 'react'
const All: React.FC = () => {
return <></>
}
export default All

View File

@@ -1,4 +1,5 @@
import React from 'react'
import tools from '@/router/tools'
const routes: RouteObject[] = [
{
@@ -15,15 +16,26 @@ const routes: RouteObject[] = [
id: 'loading',
Component: React.lazy(() => import('@/components/common/LoadingMask'))
},
{
path: '/tools',
id: 'toolsFramework',
Component: React.lazy(() => import('@/pages/ToolsFramework')),
children: tools,
handle: {
name: '工具',
title: '工具',
auth: false
}
},
{
path: '',
id: 'mainFramework',
Component: React.lazy(() => import('@/pages/MainFramework')),
id: 'homeFramework',
Component: React.lazy(() => import('@/pages/HomeFramework')),
children: [
{
path: '',
id: 'home',
Component: React.lazy(() => import('@/pages/Home')),
Component: React.lazy(() => import('@/pages/home')),
handle: {
name: '主页',
menu: true,
@@ -32,33 +44,28 @@ const routes: RouteObject[] = [
},
{
path: 'https://blog.fatweb.top',
id: 'blog',
id: 'url-blog',
handle: {
name: '博客',
menu: true,
auth: false
menu: true
}
},
{
path: 'tools',
id: 'tools',
Component: React.lazy(() => import('@/pages/Tools')),
path: '/tools',
id: 'url-tools',
children: [
{
path: 'translation',
id: 'tools-translation',
Component: React.lazy(() => import('@/pages/tools/Translation')),
id: 'url-tools-translation',
handle: {
name: '翻译',
menu: true,
auth: false
menu: true
}
}
],
handle: {
name: '工具',
menu: true,
auth: false
menu: true
}
}
]

312
src/router/tools.tsx Normal file
View File

@@ -0,0 +1,312 @@
import React from 'react'
const defaultTitle = '氮工具'
export const toolsJsonObjects: ToolsJsonObject[] = [
{
path: '',
id: 'tools',
component: React.lazy(() => import('@/pages/tools')),
icon: React.lazy(() => import('~icons/fatweb/logo.jsx')),
name: '主页',
menu: true,
auth: false
},
{
path: 'all',
id: 'tools-all',
component: React.lazy(() => import('@/pages/tools')),
name: '全部工具',
titlePostfix: ' - 全部工具',
icon: React.lazy(() => import('~icons/fatweb/logo.jsx')),
menu: true,
auth: false
},
{
path: 'translation',
id: 'tools-translation',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false,
children: [
{
path: '1',
id: '1',
name: '翻译1',
icon: React.lazy(() => import('~icons/fatweb/logo.jsx')),
menu: true,
auth: false
},
{
path: '2',
id: '2',
name: '翻译2',
menu: true,
auth: false
}
]
},
{
path: 'translation-',
id: 'tools-translation-',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译-',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false,
children: [
{
path: '1-',
id: '1-',
name: '翻译1-',
menu: true,
auth: false
},
{
path: '2-',
id: '2-',
name: '翻译2-',
menu: true,
auth: false
}
]
},
{
path: 'translation--',
id: 'tools-translation--',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--1',
id: 'tools-translation--1',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--1',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--2',
id: 'tools-translation--2',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--2',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--3',
id: 'tools-translation--3',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--3',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--4',
id: 'tools-translation--4',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--4',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--5',
id: 'tools-translation--5',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--5',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--6',
id: 'tools-translation--6',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--6',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--7',
id: 'tools-translation--7',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--7',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--8',
id: 'tools-translation--8',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--8',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--9',
id: 'tools-translation--9',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--9',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--10',
id: 'tools-translation--10',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--10',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--1-',
id: 'tools-translation--1-',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--1-',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--2-',
id: 'tools-translation--2-',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--2-',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--3-',
id: 'tools-translation--3-',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--3-',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--4-',
id: 'tools-translation--4-',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--4-',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--5-',
id: 'tools-translation--5-',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--5-',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--6-',
id: 'tools-translation--6-',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--6-',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--7-',
id: 'tools-translation--7-',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--7-',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--8-',
id: 'tools-translation--8-',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--8-',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--9-',
id: 'tools-translation--9-',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--9-',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false
},
{
path: 'translation--10-',
id: 'tools-translation--10-',
component: React.lazy(() => import('@/pages/tools/Translation')),
name: '翻译--10-',
icon: React.lazy(() => import('~icons/fatweb/jenkins.jsx')),
menu: true,
auth: false,
children: [
{
path: '1-1',
id: '1-1',
name: '翻译1-',
menu: true,
auth: false
},
{
path: '2-1',
id: '2-1',
name: '翻译2-',
menu: true,
auth: false
}
]
}
]
const tools: RouteObject[] = toolsJsonObjects.map((value) => ({
path: value.path,
id: value.id,
Component: value.component,
handle: {
name: value.name,
titlePrefix: value.titlePrefix,
title: value.title ?? defaultTitle,
titlePostfix: value.titlePostfix,
icon: value.icon,
menu: value.menu,
auth: value.auth
},
children: value.children?.map((value) => ({
path: value.path,
id: value.id,
Component: value.component,
handle: {
name: value.name,
titlePrefix: value.titlePrefix,
title: value.title ?? defaultTitle,
titlePostfix: value.titlePostfix,
icon: value.icon,
menu: value.menu,
auth: value.auth
}
}))
}))
export default tools

View File

@@ -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 {