李金
7 months ago
18 changed files with 639 additions and 417 deletions
-
5package.json
-
24src/App.tsx
-
111src/components/error-boundary/index.tsx
-
25src/components/error/403.tsx
-
25src/components/error/404.tsx
-
37src/components/error/error.tsx
-
13src/components/page-loading/index.tsx
-
16src/layout/EmptyLayout.tsx
-
255src/layout/RootLayout.tsx
-
18src/layout/_authenticated.tsx
-
30src/pages/dashboard/index.tsx
-
15src/pages/login/index.tsx
-
19src/request.ts
-
403src/routes.tsx
-
11src/store/system.ts
-
8src/types.d.ts
-
7src/utils/auth.ts
-
34yarn.lock
@ -1,16 +1,26 @@ |
|||
import { appAtom, appStore, menuDataAtom } from '@/store/system.ts' |
|||
import { Provider, useAtom, useAtomValue } from 'jotai' |
|||
import './App.css' |
|||
import { RootProvider } from './routes.tsx' |
|||
import { Provider } from 'jotai' |
|||
import { appStore } from '@/store/system.ts' |
|||
|
|||
function App() { |
|||
|
|||
const [ , ] = useAtom(appAtom) |
|||
const { data, isError, isPending } = useAtomValue(menuDataAtom) |
|||
|
|||
return ( |
|||
<Provider store={appStore}> |
|||
<RootProvider/> |
|||
</Provider> |
|||
) |
|||
if (isError) { |
|||
return <div>Error</div> |
|||
} |
|||
|
|||
if (isPending) { |
|||
return <div>Loading...</div> |
|||
} |
|||
|
|||
return ( |
|||
<Provider store={appStore}> |
|||
<RootProvider context={{ menuData: data }}/> |
|||
</Provider> |
|||
) |
|||
} |
|||
|
|||
export default App |
@ -1,64 +1,65 @@ |
|||
import React, { ErrorInfo } from 'react' |
|||
import { Button, Result } from 'antd' |
|||
import React, { ErrorInfo } from 'react' |
|||
|
|||
export class ErrorBoundary extends React.Component< |
|||
Record<string, any>, |
|||
{ hasError: boolean; errorInfo: string } |
|||
Record<string, any>, |
|||
{ hasError: boolean; errorInfo: string } |
|||
> { |
|||
state = { hasError: false, errorInfo: '' } |
|||
static getDerivedStateFromError(error: Error) { |
|||
return { hasError: true, errorInfo: error.message } |
|||
} |
|||
|
|||
static getDerivedStateFromError(error: Error) { |
|||
return { hasError: true, errorInfo: error.message } |
|||
} |
|||
componentDidCatch(error: any, errorInfo: ErrorInfo) { |
|||
// You can also log the error to an error reporting service
|
|||
// eslint-disable-next-line no-console
|
|||
console.log(error, errorInfo) |
|||
|
|||
componentDidCatch(error: any, errorInfo: ErrorInfo) { |
|||
// You can also log the error to an error reporting service
|
|||
// eslint-disable-next-line no-console
|
|||
console.log(error, errorInfo) |
|||
} |
|||
} |
|||
|
|||
render() { |
|||
if (this.state.hasError) { |
|||
// You can render any custom fallback UI
|
|||
return ( |
|||
<Result |
|||
style={{ |
|||
height: '100%', |
|||
background: '#fff', |
|||
}} |
|||
title="错误信息" |
|||
extra={ |
|||
<> |
|||
<div |
|||
style={{ |
|||
maxWidth: 620, |
|||
textAlign: 'start', |
|||
backgroundColor: 'rgba(255,229,100,0.3)', |
|||
borderInlineStartColor: '#ffe564', |
|||
borderInlineStartWidth: '9px', |
|||
borderInlineStartStyle: 'solid', |
|||
padding: '20px 45px 20px 26px', |
|||
margin: 'auto', |
|||
marginBlockEnd: '30px', |
|||
marginBlockStart: '20px', |
|||
}} |
|||
> |
|||
<p>{this.state.errorInfo}</p> |
|||
</div> |
|||
<Button |
|||
danger |
|||
type="primary" |
|||
onClick={() => { |
|||
window.location.reload() |
|||
}} |
|||
> |
|||
刷新页面 |
|||
</Button> |
|||
</> |
|||
} |
|||
/> |
|||
) |
|||
} |
|||
return this.props.children |
|||
render() { |
|||
if (this.state.hasError) { |
|||
// You can render any custom fallback UI
|
|||
return ( |
|||
<Result |
|||
style={{ |
|||
height: '100%', |
|||
background: '#fff', |
|||
}} |
|||
title="错误信息" |
|||
extra={ |
|||
<> |
|||
<div |
|||
style={{ |
|||
maxWidth: 620, |
|||
textAlign: 'start', |
|||
backgroundColor: 'rgba(255,229,100,0.3)', |
|||
borderInlineStartColor: '#ffe564', |
|||
borderInlineStartWidth: '9px', |
|||
borderInlineStartStyle: 'solid', |
|||
padding: '20px 45px 20px 26px', |
|||
margin: 'auto', |
|||
marginBlockEnd: '30px', |
|||
marginBlockStart: '20px', |
|||
}} |
|||
> |
|||
<p>{this.state.errorInfo}</p> |
|||
</div> |
|||
<Button |
|||
danger |
|||
type="primary" |
|||
onClick={() => { |
|||
window.location.reload() |
|||
}} |
|||
> |
|||
刷新页面 |
|||
</Button> |
|||
</> |
|||
} |
|||
/> |
|||
) |
|||
} |
|||
return this.props.children |
|||
} |
|||
|
|||
state = { hasError: false, errorInfo: '' } |
|||
} |
@ -0,0 +1,25 @@ |
|||
import { useNavigate } from '@tanstack/react-router' |
|||
import { Button, Result } from 'antd' |
|||
|
|||
const NotPermission = () => { |
|||
|
|||
const navigate = useNavigate() |
|||
|
|||
return ( |
|||
<Result |
|||
className="no-permission-page" |
|||
status="403" |
|||
title="403" |
|||
subTitle="Sorry, you are not authorized to access this page." |
|||
extra={ |
|||
<Button type="primary" onClick={() => navigate({ |
|||
to: '../' |
|||
})}> |
|||
Go Back |
|||
</Button> |
|||
} |
|||
/> |
|||
) |
|||
} |
|||
|
|||
export default NotPermission |
@ -0,0 +1,25 @@ |
|||
import { useNavigate } from '@tanstack/react-router' |
|||
import { Button, Result } from 'antd' |
|||
|
|||
const NotFound = () => { |
|||
|
|||
const navigate = useNavigate() |
|||
|
|||
return ( |
|||
<Result |
|||
className="error-page" |
|||
status="404" |
|||
title="404" |
|||
subTitle="Sorry, the page you visited does not exist." |
|||
extra={ |
|||
<Button type="primary" onClick={() => navigate({ |
|||
to: '../' |
|||
})}> |
|||
Go Back |
|||
</Button> |
|||
} |
|||
/> |
|||
) |
|||
} |
|||
|
|||
export default NotFound |
@ -0,0 +1,37 @@ |
|||
import { Result } from 'antd' |
|||
|
|||
|
|||
const ErrorPage = ({ error }: { error: any, reset?: string }) => { |
|||
return ( |
|||
<Result |
|||
style={{ |
|||
height: '100%', |
|||
background: '#fff', |
|||
}} |
|||
status={'error'} |
|||
title="错误信息" |
|||
extra={ |
|||
<> |
|||
<div |
|||
style={{ |
|||
maxWidth: 620, |
|||
textAlign: 'start', |
|||
backgroundColor: 'rgba(255,229,100,0.3)', |
|||
borderInlineStartColor: '#ffe564', |
|||
borderInlineStartWidth: '9px', |
|||
borderInlineStartStyle: 'solid', |
|||
padding: '20px 45px 20px 26px', |
|||
margin: 'auto', |
|||
marginBlockEnd: '30px', |
|||
marginBlockStart: '20px', |
|||
}} |
|||
> |
|||
<p>{error?.message}</p> |
|||
</div> |
|||
</> |
|||
} |
|||
/> |
|||
) |
|||
} |
|||
|
|||
export default ErrorPage |
@ -0,0 +1,13 @@ |
|||
import { Spin } from 'antd' |
|||
|
|||
const PageLoading = () => { |
|||
return ( |
|||
<> |
|||
<Spin spinning={true}> |
|||
<div style={{ height: '100vh', width: '100vh' }}></div> |
|||
</Spin> |
|||
</> |
|||
) |
|||
} |
|||
|
|||
export default PageLoading |
@ -1,11 +1,15 @@ |
|||
import { Outlet } from '@tanstack/react-router' |
|||
import ErrorPage from '@/components/error/error.tsx' |
|||
import { CatchBoundary, Outlet } from '@tanstack/react-router' |
|||
|
|||
const EmptyLayout = () => { |
|||
return ( |
|||
<> |
|||
<Outlet/> |
|||
</> |
|||
) |
|||
return ( |
|||
<CatchBoundary |
|||
getResetKey={() => 'reset-layout'} |
|||
errorComponent={ErrorPage} |
|||
> |
|||
<Outlet/> |
|||
</CatchBoundary> |
|||
) |
|||
} |
|||
|
|||
export default EmptyLayout |
@ -1,144 +1,147 @@ |
|||
import { |
|||
ProConfigProvider, |
|||
ProLayout, |
|||
} from '@ant-design/pro-components' |
|||
import PageBreadcrumb from '@/components/breadcrumb' |
|||
import { ErrorBoundary } from '@/components/error-boundary' |
|||
import ErrorPage from '@/components/error/error.tsx' |
|||
import { MenuItem } from '@/types' |
|||
import { ProConfigProvider, ProLayout, } from '@ant-design/pro-components' |
|||
import { CatchBoundary, Link, Outlet, useRouteContext } from '@tanstack/react-router' |
|||
import { ConfigProvider, Dropdown } from 'antd' |
|||
import { useState } from 'react' |
|||
import defaultProps from './_defaultProps' |
|||
import { Link, Outlet, useRouteContext } from '@tanstack/react-router' |
|||
import Icon from '../components/icon' |
|||
import { MenuItem } from '@/types' |
|||
import PageBreadcrumb from '@/components/breadcrumb' |
|||
import { ErrorBoundary } from '@/components/error-boundary' |
|||
import defaultProps from './_defaultProps' |
|||
|
|||
|
|||
//根据menuData生成Breadcrumb所需的数据
|
|||
const getBreadcrumbData = (menuData: MenuItem[], pathname: string) => { |
|||
const breadcrumbData: any[] = [] |
|||
const findItem = (menuData: any[], pathname: string) => { |
|||
for (let i = 0; i < menuData.length; i++) { |
|||
if (menuData[i].path === pathname) { |
|||
breadcrumbData.push(menuData[i]) |
|||
return true |
|||
} |
|||
if (menuData[i].children) { |
|||
if (findItem(menuData[i].children, pathname)) { |
|||
breadcrumbData.push(menuData[i]) |
|||
return true |
|||
} |
|||
} |
|||
const breadcrumbData: any[] = [] |
|||
const findItem = (menuData: any[], pathname: string) => { |
|||
for (let i = 0; i < menuData.length; i++) { |
|||
if (menuData[i].path === pathname) { |
|||
breadcrumbData.push(menuData[i]) |
|||
return true |
|||
} |
|||
if (menuData[i].children) { |
|||
if (findItem(menuData[i].children, pathname)) { |
|||
breadcrumbData.push(menuData[i]) |
|||
return true |
|||
} |
|||
return false |
|||
} |
|||
} |
|||
findItem(menuData, pathname) |
|||
return breadcrumbData.reverse() |
|||
return false |
|||
} |
|||
findItem(menuData, pathname) |
|||
return breadcrumbData.reverse() |
|||
} |
|||
|
|||
export default () => { |
|||
|
|||
const { menuData } = useRouteContext({ |
|||
from: undefined, |
|||
strict: false, |
|||
select: (state) => state |
|||
}) |
|||
const { menuData } = useRouteContext({ |
|||
from: undefined, |
|||
strict: false, |
|||
select: (state) => state |
|||
}) |
|||
|
|||
const items = getBreadcrumbData(menuData, location.pathname) |
|||
const items = getBreadcrumbData(menuData, location.pathname) |
|||
|
|||
const [ pathname, setPathname ] = useState(location.pathname) |
|||
const [ pathname, setPathname ] = useState(location.pathname) |
|||
|
|||
return ( |
|||
<div |
|||
id="crazy-pro-layout" |
|||
style={{ |
|||
height: '100vh', |
|||
// overflow: 'auto',
|
|||
}} |
|||
> |
|||
<ProConfigProvider hashed={false}> |
|||
<ConfigProvider |
|||
getTargetContainer={() => { |
|||
return document.getElementById('crazy-pro-layout') || document.body |
|||
}} |
|||
return ( |
|||
<div |
|||
id="crazy-pro-layout" |
|||
style={{ |
|||
height: '100vh', |
|||
// overflow: 'auto',
|
|||
}} |
|||
> |
|||
<CatchBoundary |
|||
getResetKey={() => 'reset-page'} |
|||
errorComponent={ErrorPage} |
|||
> |
|||
<ProConfigProvider hashed={false}> |
|||
<ConfigProvider |
|||
getTargetContainer={() => { |
|||
return document.getElementById('crazy-pro-layout') || document.body |
|||
}} |
|||
> |
|||
<ProLayout |
|||
headerContentRender={() => <PageBreadcrumb |
|||
className={'top-breadcrumb'} |
|||
showIcon={false} |
|||
items={items}/>} |
|||
title="Crazy Pro" |
|||
{...defaultProps} |
|||
route={{ |
|||
path: '/', |
|||
routes: menuData |
|||
}} |
|||
location={{ |
|||
pathname, |
|||
}} |
|||
token={{ |
|||
header: { |
|||
colorBgMenuItemSelected: 'rgba(0,0,0,0.04)', |
|||
}, |
|||
}} |
|||
menu={{ |
|||
collapsedShowGroupTitle: true, |
|||
}} |
|||
avatarProps={{ |
|||
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg', |
|||
size: 'small', |
|||
title: '管理员', |
|||
render: (_, dom) => { |
|||
return ( |
|||
<Dropdown |
|||
menu={{ |
|||
items: [ |
|||
{ |
|||
key: 'logout', |
|||
icon: <Icon type={'Logout'}/>, |
|||
label: '退出登录', |
|||
}, |
|||
], |
|||
}} |
|||
> |
|||
<ProLayout |
|||
headerContentRender={() => <PageBreadcrumb |
|||
className={'top-breadcrumb'} |
|||
showIcon={false} |
|||
items={items}/>} |
|||
title="Crazy Pro" |
|||
{...defaultProps} |
|||
route={{ |
|||
path: '/', |
|||
routes: menuData |
|||
}} |
|||
location={{ |
|||
pathname, |
|||
}} |
|||
token={{ |
|||
header: { |
|||
colorBgMenuItemSelected: 'rgba(0,0,0,0.04)', |
|||
}, |
|||
}} |
|||
menu={{ |
|||
collapsedShowGroupTitle: true, |
|||
}} |
|||
avatarProps={{ |
|||
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg', |
|||
size: 'small', |
|||
title: '管理员', |
|||
render: (_, dom) => { |
|||
return ( |
|||
<Dropdown |
|||
menu={{ |
|||
items: [ |
|||
{ |
|||
key: 'logout', |
|||
icon: <Icon type={'Logout'}/>, |
|||
label: '退出登录', |
|||
}, |
|||
], |
|||
}} |
|||
> |
|||
{dom} |
|||
</Dropdown> |
|||
) |
|||
}, |
|||
}} |
|||
actionsRender={(props) => { |
|||
if (props.isMobile) return [] |
|||
if (typeof window === 'undefined') return [] |
|||
return [] |
|||
}} |
|||
menuRender={(_, defaultDom) => ( |
|||
<> |
|||
{defaultDom} |
|||
</> |
|||
)} |
|||
menuItemRender={(item, dom) => { |
|||
return <div onClick={() => { |
|||
setPathname(item.path || '/welcome') |
|||
}} |
|||
> |
|||
<Link to={item.path} target={item.type === 3 ? '_blank': '_self' }> |
|||
{dom} |
|||
</Link> |
|||
</div> |
|||
}} |
|||
{...{ |
|||
'layout': 'mix', |
|||
'navTheme': 'light', |
|||
'contentWidth': 'Fluid', |
|||
'fixSiderbar': true, |
|||
'colorPrimary': '#1677FF', |
|||
'siderMenuType': 'group', |
|||
// layout: 'side',
|
|||
}} |
|||
ErrorBoundary={ErrorBoundary} |
|||
> |
|||
<Outlet/> |
|||
</ProLayout> |
|||
</ConfigProvider> |
|||
</ProConfigProvider> |
|||
</div> |
|||
) |
|||
{dom} |
|||
</Dropdown> |
|||
) |
|||
}, |
|||
}} |
|||
actionsRender={(props) => { |
|||
if (props.isMobile) return [] |
|||
if (typeof window === 'undefined') return [] |
|||
return [] |
|||
}} |
|||
menuRender={(_, defaultDom) => ( |
|||
<> |
|||
{defaultDom} |
|||
</> |
|||
)} |
|||
menuItemRender={(item, dom) => { |
|||
return <div onClick={() => { |
|||
setPathname(item.path || '/welcome') |
|||
}} |
|||
> |
|||
<Link to={item.path} target={item.type === 3 ? '_blank' : '_self'}> |
|||
{dom} |
|||
</Link> |
|||
</div> |
|||
}} |
|||
{...{ |
|||
'layout': 'mix', |
|||
'navTheme': 'light', |
|||
'contentWidth': 'Fluid', |
|||
'fixSiderbar': true, |
|||
'colorPrimary': '#1677FF', |
|||
'siderMenuType': 'group', |
|||
// layout: 'side',
|
|||
}} |
|||
ErrorBoundary={ErrorBoundary} |
|||
> |
|||
<Outlet/> |
|||
</ProLayout> |
|||
</ConfigProvider> |
|||
</ProConfigProvider> |
|||
</CatchBoundary> |
|||
</div> |
|||
) |
|||
} |
@ -0,0 +1,18 @@ |
|||
import { isAuthenticated } from '@/utils/auth.ts' |
|||
import { createFileRoute,redirect } from '@tanstack/react-router' |
|||
|
|||
export const Route = createFileRoute('/_authenticated')({ |
|||
beforeLoad: async ({ location }) => { |
|||
if (!isAuthenticated()) { |
|||
throw redirect({ |
|||
to: '/login', |
|||
search: { |
|||
// Use the current location to power a redirect after login
|
|||
// (Do not use `router.state.resolvedLocation` as it can
|
|||
// potentially lag behind the actual current location)
|
|||
redirect: location.href, |
|||
}, |
|||
}) |
|||
} |
|||
}, |
|||
}) |
@ -1,25 +1,25 @@ |
|||
import { ProCard } from '@ant-design/pro-components' |
|||
import { createLazyRoute } from '@tanstack/react-router' |
|||
import {ProCard} from '@ant-design/pro-components' |
|||
import {createFileRoute} from '@tanstack/react-router' |
|||
|
|||
|
|||
const Index = () => { |
|||
return ( |
|||
<> |
|||
<ProCard |
|||
style={{ |
|||
height: '100vh', |
|||
minHeight: 800, |
|||
}} |
|||
> |
|||
<> |
|||
<ProCard |
|||
style={{ |
|||
height: '100vh', |
|||
minHeight: 800, |
|||
}} |
|||
> |
|||
|
|||
<h1>Dashboard</h1> |
|||
<h1>Dashboard</h1> |
|||
|
|||
</ProCard> |
|||
</> |
|||
</ProCard> |
|||
</> |
|||
) |
|||
} |
|||
|
|||
export const Route = createLazyRoute('/welcome')({ |
|||
component: Index, |
|||
}) |
|||
export const Route = createFileRoute('/dashboard')({ |
|||
component: Index, |
|||
}) |
|||
export default Index |
@ -1,15 +1,16 @@ |
|||
import { createFileRoute } from '@tanstack/react-router' |
|||
|
|||
const Login = () => { |
|||
return ( |
|||
<div> |
|||
Login |
|||
</div> |
|||
) |
|||
|
|||
return ( |
|||
<div> |
|||
{} |
|||
</div> |
|||
) |
|||
} |
|||
|
|||
export const Route = createFileRoute("/login")({ |
|||
component: Login |
|||
export const Route = createFileRoute('/login')({ |
|||
component: Login |
|||
}) |
|||
|
|||
export default Login |
@ -1,239 +1,286 @@ |
|||
import NotPermission from '@/components/error/403.tsx' |
|||
import NotFound from '@/components/error/404.tsx' |
|||
import ErrorPage from '@/components/error/error.tsx' |
|||
import PageLoading from '@/components/page-loading' |
|||
import { Route as AuthenticatedImport } from '@/layout/_authenticated.tsx' |
|||
import EmptyLayout from '@/layout/EmptyLayout.tsx' |
|||
import ListPageLayout from '@/layout/ListPageLayout.tsx' |
|||
import { Route as DashboardImport } from '@/pages/dashboard' |
|||
import { Route as LoginRouteImport } from '@/pages/login' |
|||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' |
|||
import { |
|||
createRouter, |
|||
createRoute, |
|||
RouterProvider, AnyRoute, redirect, createRootRouteWithContext, createLazyRoute, Outlet, |
|||
AnyRoute, |
|||
createLazyRoute, |
|||
createRootRouteWithContext, |
|||
createRoute, |
|||
createRouter, |
|||
Outlet, |
|||
redirect, |
|||
RouterProvider, |
|||
} from '@tanstack/react-router' |
|||
import { TanStackRouterDevtools } from '@tanstack/router-devtools' |
|||
import { memo } from 'react' |
|||
import RootLayout from './layout/RootLayout' |
|||
import ListPageLayout from '@/layout/ListPageLayout.tsx' |
|||
import { Route as LoginRouteImport } from '@/pages/login' |
|||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' |
|||
import { useAtomValue } from 'jotai' |
|||
|
|||
import { IRootContext, MenuItem } from './types' |
|||
import { appAtom, menuDataAtom } from './store/system.ts' |
|||
import { useAtom } from 'jotai/index' |
|||
import EmptyLayout from '@/layout/EmptyLayout.tsx' |
|||
|
|||
|
|||
export const queryClient = new QueryClient({ |
|||
defaultOptions: { |
|||
queries: { |
|||
retry: false, |
|||
} |
|||
defaultOptions: { |
|||
queries: { |
|||
retry: false, |
|||
} |
|||
} |
|||
}) |
|||
|
|||
|
|||
const rootRoute = createRootRouteWithContext<IRootContext>()({ |
|||
component: () => ( |
|||
<div> |
|||
<Outlet/> |
|||
<TanStackRouterDevtools position={'bottom-right'}/> |
|||
</div> |
|||
), |
|||
beforeLoad: ({ location }) => { |
|||
if (location.pathname === '/') { |
|||
return redirect({ to: '/welcome' }) |
|||
} |
|||
}, |
|||
notFoundComponent: () => <div>404 Not Found</div>, |
|||
component: () => ( |
|||
<div> |
|||
<Outlet/> |
|||
<TanStackRouterDevtools position={'bottom-right'}/> |
|||
</div> |
|||
), |
|||
beforeLoad: ({ location }) => { |
|||
if (location.pathname === '/') { |
|||
return redirect({ to: '/dashboard' }) |
|||
} |
|||
}, |
|||
notFoundComponent: NotFound, |
|||
pendingComponent: PageLoading, |
|||
errorComponent: ({ error }) => <ErrorPage error={error}/>, |
|||
}) |
|||
|
|||
const emptyRoute = createRoute({ |
|||
getParentRoute: () => rootRoute, |
|||
id: '/empty', |
|||
component: EmptyLayout, |
|||
getParentRoute: () => rootRoute, |
|||
id: '/_empty', |
|||
component: EmptyLayout, |
|||
}) |
|||
|
|||
const layoutRoute = createRoute({ |
|||
getParentRoute: () => rootRoute, |
|||
id: '/layout', |
|||
component: RootLayout, |
|||
const authRoute = AuthenticatedImport.update({ |
|||
getParentRoute: () => rootRoute, |
|||
id: '/_authenticated', |
|||
} as any) |
|||
|
|||
const layoutNormalRoute = createRoute({ |
|||
getParentRoute: () => rootRoute, |
|||
id: '/_normal_layout', |
|||
component: RootLayout, |
|||
}) |
|||
|
|||
const layoutAuthRoute = createRoute({ |
|||
getParentRoute: () => authRoute, |
|||
id: '/_auth_layout', |
|||
component: RootLayout, |
|||
}) |
|||
|
|||
const notAuthRoute = createRoute({ |
|||
getParentRoute: () => layoutNormalRoute, |
|||
path: '/not-auth', |
|||
component: NotPermission |
|||
}) |
|||
|
|||
const dashboardRoute = DashboardImport.update({ |
|||
path: '/dashboard', |
|||
getParentRoute: () => layoutAuthRoute, |
|||
} as any) |
|||
|
|||
const loginRoute = LoginRouteImport.update({ |
|||
path: '/login', |
|||
getParentRoute: () => emptyRoute, |
|||
path: '/login', |
|||
getParentRoute: () => emptyRoute, |
|||
} as any) |
|||
|
|||
const menusRoute = createRoute({ |
|||
getParentRoute: () => layoutRoute, |
|||
path: '/system/menus', |
|||
getParentRoute: () => layoutAuthRoute, |
|||
path: '/system/menus', |
|||
}).lazy(async () => await import('@/pages/system/menus').then(d => d.Route)) |
|||
|
|||
const departmentsRoute = createRoute({ |
|||
getParentRoute: () => layoutRoute, |
|||
path: '/system/departments', |
|||
getParentRoute: () => layoutAuthRoute, |
|||
path: '/system/departments', |
|||
}).lazy(async () => await import('@/pages/system/departments').then(d => d.Route)) |
|||
|
|||
const usersRoute = createRoute({ |
|||
getParentRoute: () => layoutRoute, |
|||
path: '/system/users', |
|||
getParentRoute: () => layoutAuthRoute, |
|||
path: '/system/users', |
|||
}).lazy(async () => await import('@/pages/system/users').then(d => d.Route)) |
|||
|
|||
const rolesRoute = createRoute({ |
|||
getParentRoute: () => layoutRoute, |
|||
path: '/system/roles', |
|||
getParentRoute: () => layoutAuthRoute, |
|||
path: '/system/roles', |
|||
}).lazy(async () => await import('@/pages/system/roles').then(d => d.Route)) |
|||
|
|||
|
|||
declare module '@tanstack/react-router' { |
|||
interface FileRoutesByPath { |
|||
'/login': { |
|||
preLoaderRoute: typeof LoginRouteImport |
|||
parentRoute: typeof rootRoute |
|||
}, |
|||
'/system/menus': { |
|||
preLoaderRoute: typeof menusRoute |
|||
parentRoute: typeof layoutRoute |
|||
}, |
|||
'/system/departments': { |
|||
preLoaderRoute: typeof departmentsRoute |
|||
parentRoute: typeof layoutRoute |
|||
}, |
|||
'/system/users': { |
|||
preLoaderRoute: typeof usersRoute |
|||
parentRoute: typeof layoutRoute |
|||
}, |
|||
'/system/roles': { |
|||
preLoaderRoute: typeof rolesRoute |
|||
parentRoute: typeof layoutRoute |
|||
}, |
|||
'/welcome': { |
|||
preLoaderRoute: typeof rootRoute |
|||
parentRoute: typeof layoutRoute |
|||
}, |
|||
} |
|||
interface FileRoutesByPath { |
|||
'/_authenticated': { |
|||
preLoaderRoute: typeof AuthenticatedImport |
|||
parentRoute: typeof rootRoute |
|||
}, |
|||
'/_normal_layout': { |
|||
preLoaderRoute: typeof layoutNormalRoute |
|||
parentRoute: typeof rootRoute |
|||
}, |
|||
'/_layout': { |
|||
preLoaderRoute: typeof layoutAuthRoute |
|||
parentRoute: typeof rootRoute |
|||
}, |
|||
'/': { |
|||
preLoaderRoute: typeof DashboardImport |
|||
parentRoute: typeof layoutAuthRoute |
|||
}, |
|||
'/dashboard': { |
|||
preLoaderRoute: typeof DashboardImport |
|||
parentRoute: typeof layoutAuthRoute |
|||
}, |
|||
'/login': { |
|||
preLoaderRoute: typeof LoginRouteImport |
|||
parentRoute: typeof rootRoute |
|||
}, |
|||
'/system/menus': { |
|||
preLoaderRoute: typeof menusRoute |
|||
parentRoute: typeof layoutAuthRoute |
|||
}, |
|||
'/system/departments': { |
|||
preLoaderRoute: typeof departmentsRoute |
|||
parentRoute: typeof layoutAuthRoute |
|||
}, |
|||
'/system/users': { |
|||
preLoaderRoute: typeof usersRoute |
|||
parentRoute: typeof layoutAuthRoute |
|||
}, |
|||
'/system/roles': { |
|||
preLoaderRoute: typeof rolesRoute |
|||
parentRoute: typeof layoutAuthRoute |
|||
}, |
|||
'/welcome': { |
|||
preLoaderRoute: typeof rootRoute |
|||
parentRoute: typeof layoutAuthRoute |
|||
}, |
|||
} |
|||
} |
|||
|
|||
|
|||
const routeTree = rootRoute.addChildren( |
|||
[ |
|||
loginRoute, |
|||
emptyRoute, |
|||
layoutRoute.addChildren( |
|||
[ |
|||
menusRoute, |
|||
departmentsRoute, |
|||
usersRoute, |
|||
rolesRoute, |
|||
] |
|||
), |
|||
] |
|||
[ |
|||
//非Layout
|
|||
loginRoute, |
|||
emptyRoute, |
|||
|
|||
//不带权限Layout
|
|||
layoutNormalRoute.addChildren([ |
|||
notAuthRoute, |
|||
]), |
|||
|
|||
//带权限Layout
|
|||
dashboardRoute, |
|||
authRoute.addChildren( |
|||
[ |
|||
layoutAuthRoute.addChildren( |
|||
[ |
|||
menusRoute, |
|||
departmentsRoute, |
|||
usersRoute, |
|||
rolesRoute, |
|||
] |
|||
), |
|||
] |
|||
) |
|||
] |
|||
) |
|||
|
|||
|
|||
export const generateDynamicRoutes = (menuData: MenuItem[]) => { |
|||
// 递归生成路由,如果有routes则递归生成子路由
|
|||
// 递归生成路由,如果有routes则递归生成子路由
|
|||
|
|||
const generateRoutes = (menu: MenuItem, parentRoute: AnyRoute) => { |
|||
const generateRoutes = (menu: MenuItem, parentRoute: AnyRoute) => { |
|||
|
|||
const path = menu.path?.replace(parentRoute.options?.path, '') |
|||
const isLayout = menu.children && menu.children.length > 0 && menu.type === 'menu' |
|||
const path = menu.path?.replace(parentRoute.options?.path, '') |
|||
const isLayout = menu.children && menu.children.length > 0 && menu.type === 'menu' |
|||
|
|||
if (isLayout && !menu.component) { |
|||
//没有component的layout,直接返回
|
|||
return createRoute({ |
|||
getParentRoute: () => parentRoute, |
|||
id: path!, |
|||
component: ListPageLayout, |
|||
}) |
|||
} |
|||
if (isLayout && !menu.component) { |
|||
//没有component的layout,直接返回
|
|||
return createRoute({ |
|||
getParentRoute: () => parentRoute, |
|||
id: path!, |
|||
component: ListPageLayout, |
|||
}) |
|||
} |
|||
|
|||
// @ts-ignore 添加menu属性,方便后面获取
|
|||
const options = { |
|||
getParentRoute: () => parentRoute, |
|||
menu, |
|||
} as any |
|||
// @ts-ignore 添加menu属性,方便后面获取
|
|||
const options = { |
|||
getParentRoute: () => parentRoute, |
|||
menu, |
|||
} as any |
|||
|
|||
if (isLayout) { |
|||
options.id = path! |
|||
} else { |
|||
options.path = path! |
|||
} |
|||
if (isLayout) { |
|||
options.id = path! |
|||
} else { |
|||
options.path = path! |
|||
} |
|||
|
|||
//删除掉parentRoute的path,避免重复
|
|||
const route = createRoute(options).lazy(async () => { |
|||
|
|||
// @ts-ignore 获取route中的menu属性
|
|||
const menu = route.options.menu as MenuItem |
|||
let component = menu.component |
|||
|
|||
// menu.type
|
|||
// 1,组件(页面),2,IFrame,3,外链接,4,按钮
|
|||
if (menu.type === 'iframe') { |
|||
component = '@/components/Iframe' |
|||
} |
|||
|
|||
if (!component) { |
|||
return createLazyRoute(menu.path)({ |
|||
component: () => <div>404 Not Found</div> |
|||
}) |
|||
} |
|||
/* @vite-ignore */ |
|||
const d = await import(`${component}`) |
|||
if (d.Route) { |
|||
return d.Route |
|||
} |
|||
if (d.GenRoute) { |
|||
return d.GenRoute(menu.path) |
|||
} |
|||
return createLazyRoute(menu.path)({ |
|||
component: d.default || d |
|||
}) |
|||
}) |
|||
return route |
|||
//删除掉parentRoute的path,避免重复
|
|||
const route = createRoute(options).lazy(async () => { |
|||
|
|||
} |
|||
// @ts-ignore 获取route中的menu属性
|
|||
const menu = route.options.menu as MenuItem |
|||
let component = menu.component |
|||
|
|||
// menu.type
|
|||
// 1,组件(页面),2,IFrame,3,外链接,4,按钮
|
|||
if (menu.type === 'iframe') { |
|||
component = '@/components/Iframe' |
|||
} |
|||
|
|||
// 对menuData递归生成路由,只处理type =1 的菜单
|
|||
const did = (menus: MenuItem[], parentRoute: AnyRoute) => { |
|||
return menus.filter((item) => item.type === 'menu').map((item) => { |
|||
// 如果有children则递归生成子路由,同样只处理type =1 的菜单
|
|||
const route = generateRoutes(item, parentRoute) |
|||
if (item.children && item.children.length > 0) { |
|||
const children = did(item.children, route) |
|||
if (children.length > 0) { |
|||
route.addChildren(children) |
|||
} |
|||
} |
|||
return route |
|||
if (!component) { |
|||
return createLazyRoute(menu.path)({ |
|||
component: () => <div>404 Not Found</div> |
|||
}) |
|||
} |
|||
} |
|||
/* @vite-ignore */ |
|||
const d = await import(`${component}`) |
|||
if (d.Route) { |
|||
return d.Route |
|||
} |
|||
if (d.GenRoute) { |
|||
return d.GenRoute(menu.path) |
|||
} |
|||
return createLazyRoute(menu.path)({ |
|||
component: d.default || d |
|||
}) |
|||
}) |
|||
return route |
|||
|
|||
} |
|||
|
|||
// 对menuData递归生成路由,只处理type =1 的菜单
|
|||
const did = (menus: MenuItem[], parentRoute: AnyRoute) => { |
|||
return menus.filter((item) => item.type === 'menu').map((item) => { |
|||
// 如果有children则递归生成子路由,同样只处理type =1 的菜单
|
|||
const route = generateRoutes(item, parentRoute) |
|||
if (item.children && item.children.length > 0) { |
|||
const children = did(item.children, route) |
|||
if (children.length > 0) { |
|||
route.addChildren(children) |
|||
} |
|||
} |
|||
return route |
|||
}) |
|||
} |
|||
|
|||
return did(menuData, rootRoute) |
|||
return did(menuData, rootRoute) |
|||
} |
|||
|
|||
const router = createRouter({ |
|||
routeTree, |
|||
context: { queryClient, menuData: undefined }, |
|||
defaultPreload: 'intent' |
|||
routeTree, |
|||
context: { queryClient, menuData: [] }, |
|||
defaultPreload: 'intent' |
|||
}) |
|||
|
|||
export const RootProvider = () => { |
|||
|
|||
const [ , ] = useAtom(appAtom) |
|||
const { data, isError, isPending } = useAtomValue(menuDataAtom) |
|||
|
|||
if (isError) { |
|||
return <div>Error</div> |
|||
} |
|||
export const RootProvider = memo((props: { context: Partial<IRootContext> }) => { |
|||
|
|||
if (isPending) { |
|||
return <div>Loading...</div> |
|||
} |
|||
|
|||
router.update({ |
|||
context: { |
|||
queryClient, |
|||
menuData: data, |
|||
} |
|||
}) |
|||
|
|||
return ( |
|||
<QueryClientProvider client={queryClient}> |
|||
<RouterProvider router={router}/> |
|||
</QueryClientProvider> |
|||
) |
|||
} |
|||
return ( |
|||
<QueryClientProvider client={queryClient}> |
|||
<RouterProvider router={router} context={{ ...props.context, queryClient }}/> |
|||
</QueryClientProvider> |
|||
) |
|||
}) |
@ -0,0 +1,7 @@ |
|||
import { getToken } from '@/store/system.ts' |
|||
|
|||
|
|||
export const isAuthenticated = () => { |
|||
const token = getToken() |
|||
return !!token |
|||
} |
@ -2068,6 +2068,27 @@ hoist-non-react-statics@^3.3.2: |
|||
dependencies: |
|||
react-is "^16.7.0" |
|||
|
|||
html-parse-stringify@^3.0.1: |
|||
version "3.0.1" |
|||
resolved "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" |
|||
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== |
|||
dependencies: |
|||
void-elements "3.1.0" |
|||
|
|||
i18next-browser-languagedetector@^7.2.1: |
|||
version "7.2.1" |
|||
resolved "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz#1968196d437b4c8db847410c7c33554f6c448f6f" |
|||
integrity sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw== |
|||
dependencies: |
|||
"@babel/runtime" "^7.23.2" |
|||
|
|||
i18next@^23.11.2: |
|||
version "23.11.2" |
|||
resolved "https://registry.npmmirror.com/i18next/-/i18next-23.11.2.tgz#4c0e8192a9ba230fe7dc68b76459816ab601826e" |
|||
integrity sha512-qMBm7+qT8jdpmmDw/kQD16VpmkL9BdL+XNAK5MNbNFaf1iQQq35ZbPrSlqmnNPOSUY4m342+c0t0evinF5l7sA== |
|||
dependencies: |
|||
"@babel/runtime" "^7.23.2" |
|||
|
|||
ignore@^5.2.0, ignore@^5.3.1: |
|||
version "5.3.1" |
|||
resolved "https://registry.npmmirror.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" |
|||
@ -2887,6 +2908,14 @@ react-dom@^18.2.0: |
|||
loose-envify "^1.1.0" |
|||
scheduler "^0.23.0" |
|||
|
|||
react-i18next@^14.1.0: |
|||
version "14.1.0" |
|||
resolved "https://registry.npmmirror.com/react-i18next/-/react-i18next-14.1.0.tgz#44da74fbffd416f5d0c5307ef31735cf10cc91d9" |
|||
integrity sha512-3KwX6LHpbvGQ+sBEntjV4sYW3Zovjjl3fpoHbUwSgFHf0uRBcbeCBLR5al6ikncI5+W0EFb71QXZmfop+J6NrQ== |
|||
dependencies: |
|||
"@babel/runtime" "^7.23.9" |
|||
html-parse-stringify "^3.0.1" |
|||
|
|||
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0: |
|||
version "16.13.1" |
|||
resolved "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" |
|||
@ -3231,6 +3260,11 @@ vite@^5.2.0: |
|||
optionalDependencies: |
|||
fsevents "~2.3.3" |
|||
|
|||
[email protected]: |
|||
version "3.1.0" |
|||
resolved "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" |
|||
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== |
|||
|
|||
warning@^4.0.3: |
|||
version "4.0.3" |
|||
resolved "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" |
|||
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue