Browse Source

优化登录,退出功能

main
dark 7 months ago
parent
commit
f178bbf5dc
  1. 58
      src/App.tsx
  2. 87
      src/components/avatar/index.tsx
  3. 123
      src/pages/login/index.css
  4. 26
      src/pages/login/index.tsx
  5. 217
      src/request.ts
  6. 415
      src/routes.tsx

58
src/App.tsx

@ -1,51 +1,37 @@
import { AppContextProvider } from '@/context.ts'
import { initI18n } from '@/i18n.ts'
import { appAtom, appStore, changeLanguage } from '@/store/system.ts'
import { userMenuDataAtom } from '@/store/user.ts'
import { IAppData } from '@/global'
import { ConfigProvider } from '@/components/config-provider'
import { Provider, useAtom, useAtomValue } from 'jotai'
import { Provider, useAtom } from 'jotai'
import './App.css'
import { useEffect } from 'react'
import { RootProvider } from './routes.tsx'
import PageLoading from '@/components/page-loading'
function App() {
const [ appData, ] = useAtom(appAtom)
const { data = [], isLoading, isFetching, refetch } = useAtomValue(userMenuDataAtom)
useEffect(() => {
initI18n()
}, [])
useEffect(() => {
if (appData.token) {
refetch().then()
}
}, [ appData.token ])
if (isLoading || isFetching) {
return <PageLoading/>
}
return (
<ConfigProvider>
<AppContextProvider value={{
get appData() {
return appData as IAppData
},
changeLanguage
}}>
<Provider store={appStore}>
<RootProvider context={{ menuData: data }}/>
</Provider>
</AppContextProvider>
</ConfigProvider>
)
const [ appData, ] = useAtom(appAtom)
useEffect(() => {
initI18n()
}, [])
return (
<ConfigProvider>
<AppContextProvider value={{
get appData() {
return appData as IAppData
},
changeLanguage
}}>
<Provider store={appStore}>
<RootProvider context={{}}/>
</Provider>
</AppContextProvider>
</ConfigProvider>
)
}
export default App

87
src/components/avatar/index.tsx

@ -1,50 +1,63 @@
import Icon from '@/components/icon'
import { useTranslation } from '@/i18n.ts'
import { currentUserAtom } from '@/store/user.ts'
import { currentUserAtom, logoutAtom } from '@/store/user.ts'
import { Avatar as AntAvatar, Dropdown, Spin } from 'antd'
import { useAtomValue } from 'jotai'
import { useNavigate } from '@tanstack/react-router'
const Avatar = () => {
const { t } = useTranslation()
const { data, isLoading } = useAtomValue(currentUserAtom)
const { t } = useTranslation()
const { data, isLoading } = useAtomValue(currentUserAtom)
const { mutate: logout } = useAtomValue(logoutAtom)
const navigate = useNavigate()
return (
<div>
<Dropdown
key={'user'}
placement="bottomRight"
menu={{
items: [
{
key: 'logout',
icon: <Icon type={'park:Logout'}/>,
label: <span style={{
marginInlineStart: 8,
userSelect: 'none'
}}>{t('app.header.logout')}</span>,
},
],
}}
>
<Spin spinning={isLoading}>
<AntAvatar
key="avatar"
size={'small'}
src={data?.avatar || data?.nickname?.substring(0, 1)}>
{!data?.avatar && data?.nickname?.substring(0, 1)}
</AntAvatar>
<span key="name"
style={{
marginInlineStart: 8,
userSelect: 'none'
}}>
return (
<div>
<Dropdown
key={'user'}
placement="bottomRight"
menu={{
items: [
{
key: 'logout',
icon: <Icon type={'park:Logout'}/>,
label: <span style={{
marginInlineStart: 8,
userSelect: 'none'
}}>{t('app.header.logout')}</span>,
},
],
onClick: (e) => {
if (e.key === 'logout') {
logout()
navigate({
to: '/login', search: {
redirect: window.location.pathname
}
})
}
},
}}
>
<Spin spinning={isLoading}>
<AntAvatar
key="avatar"
size={'small'}
src={data?.avatar || data?.nickname?.substring(0, 1)}>
{!data?.avatar && data?.nickname?.substring(0, 1)}
</AntAvatar>
<span key="name"
style={{
marginInlineStart: 8,
userSelect: 'none'
}}>
{data?.nickname}
</span>
</Spin>
</Dropdown>
</div>
)
</Spin>
</Dropdown>
</div>
)
}
export default Avatar

123
src/pages/login/index.css

@ -1,123 +0,0 @@
body {
margin: 0;
padding: 0;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
overflow: hidden;
.login-container {
display: flex;
align-items: center;
height: 100vh;
background-image: url("@/assets/login.png");
background-repeat: no-repeat;
background-size: cover;
.language {
position: absolute;
top: 10px;
right: 10px;
color: #fff;
font-size: 14px;
cursor: pointer;
}
.loginBlock {
width: 100%;
height: 100%;
padding: 40px 0;
display: flex;
align-items: center;
justify-content: center;
}
.innerBlock {
width: 356px;
margin: 0 auto;
}
.logo {
height: 30px;
}
.infoLine {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0;
}
.infoLeft {
color: #666;
font-size: 14px;
}
.desc {
margin: 24px 0;
color: #999;
font-size: 16px;
cursor: pointer;
}
.active {
color: #333;
font-weight: bold;
font-size: 24px;
}
.line {
display: inline-block;
width: 1px;
height: 12px;
background: #999;
}
.innerBeforeInput {
margin-left: 10px;
color: #999;
}
.innerBeforeInput .line {
margin-left: 10px;
}
.innerAfterInput {
margin-right: 10px;
color: #999;
}
.innerAfterInput .line {
margin-right: 10px;
vertical-align: middle;
}
.sendCode {
max-width: 65px;
margin-right: 10px;
}
.otherLogin {
color: #666;
font-size: 14px;
}
.icon {
margin-left: 10px;
}
.link {
color: #5584ff;
font-size: 14px;
text-align: left;
}
.submitBtn {
width: 100%;
}
}
}

26
src/pages/login/index.tsx

@ -1,22 +1,18 @@
import SelectLang from '@/components/select-lang'
import { createFileRoute, useSearch, useNavigate } from '@tanstack/react-router'
import { Button, Form, Input, message, Space } from 'antd'
import { useAtom } from 'jotai'
import { createFileRoute } from '@tanstack/react-router'
import { Button, Form, Input, Space } from 'antd'
import { useAtom, useAtomValue } from 'jotai'
import { useTranslation } from '@/i18n.ts'
import { loginAtom, loginFormAtom } from '@/store/user.ts'
import { memo, useEffect, useLayoutEffect } from 'react'
// import './index.css'
import { memo, useLayoutEffect } from 'react'
import { useStyles } from './style.ts'
const Login = memo(() => {
const { styles } = useStyles()
const navigator = useNavigate()
// @ts-ignore 从url中获取redirect参数
const search = useSearch({ form: '/login' })
const { t } = useTranslation()
const [ values, setValues ] = useAtom(loginFormAtom)
const [ { isPending, isSuccess, mutate } ] = useAtom(loginAtom)
const { isPending, mutate } = useAtomValue(loginAtom)
const [ form ] = Form.useForm()
const handleSubmit = () => {
@ -25,19 +21,11 @@ const Login = memo(() => {
})
}
useEffect(() => {
if (isSuccess) {
message.success(t('login.success'))
navigator({
to: search?.redirect ?? '/'
})
}
}, [ isSuccess ])
useLayoutEffect(()=>{
useLayoutEffect(() => {
document.body.className = 'login'
return ()=>{
return () => {
document.body.className = document.body.className.replace('login', '')
}

217
src/request.ts

@ -3,8 +3,8 @@ import { IApiResult } from '@/global'
import { Record } from '@icon-park/react'
import { message } from 'antd'
import axios, {
AxiosRequestConfig,
AxiosInstance, AxiosResponse,
AxiosRequestConfig,
AxiosInstance, AxiosResponse,
} from 'axios'
@ -16,128 +16,133 @@ interface RequestMethods extends Pick<AxiosInstance, 'get' | 'post' | 'put' | 'd
const axiosInstance = axios.create({
baseURL: '/api/v1',
// timeout: 1000,
headers: {
'Content-Type': 'application/json',
},
baseURL: '/api/v1',
// timeout: 1000,
headers: {
'Content-Type': 'application/json',
},
})
//拦截request,添加token
axiosInstance.interceptors.request.use((config) => {
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
return config
}, (error) => {
console.log('error', error)
return Promise.reject(error)
console.log('error', error)
return Promise.reject(error)
})
//拦截response,返回data
axiosInstance.interceptors.response.use(
(response) => {
// console.log('response', response.data)
message.destroy()
const result = response.data as IApiResult
switch (result.code) {
case 200:
//login
if (response.config.url?.includes('/sys/login')) {
setToken(result.data.token)
}
return response
case 401:
setToken('')
if (window.location.pathname === '/login') {
return Promise.reject(new Error('to login'))
}
// 401: 未登录
message.error('登录失败,跳转重新登录')
// eslint-disable-next-line no-case-declarations
const search = new URLSearchParams(window.location.search)
// eslint-disable-next-line no-case-declarations
let redirect = window.location.pathname
if (search.toString() !== '') {
redirect = window.location.pathname + '?=' + search.toString()
}
window.location.href = `/login?redirect=${encodeURIComponent(redirect)}`
return Promise.reject(new Error('to login'))
default:
message.error(result.message ?? '请求失败')
return Promise.reject(response)
}
}, (error) => {
// console.log('error', error)
const { response } = error
if (response) {
switch (response.status) {
case 401:
if (window.location.pathname === '/login') {
return
}
setToken('')
// 401: 未登录
message.error('登录失败,跳转重新登录')
// eslint-disable-next-line no-case-declarations
const search = new URLSearchParams(window.location.search)
// eslint-disable-next-line no-case-declarations
let redirect = window.location.pathname
if (search.toString() !== '') {
redirect = window.location.pathname + '?=' + search.toString()
}
window.location.href = `/login?redirect=${encodeURIComponent(redirect)}`
return
default:
message.error(response.data.message)
return Promise.reject(response)
}
}
return Promise.reject(error)
})
(response) => {
// console.log('response', response.data)
message.destroy()
const result = response.data as IApiResult
switch (result.code) {
case 200:
//login
if (response.config.url?.includes('/sys/login')) {
setToken(result.data.token)
const search = new URLSearchParams(window.location.search)
// eslint-disable-next-line no-case-declarations
const redirect = search.get('redirect')
if (redirect) {
window.location.href = redirect
}
}
return response
case 401:
setToken('')
if (window.location.pathname === '/login') {
return Promise.reject(new Error('to login'))
}
// 401: 未登录
message.error('登录失败,跳转重新登录')
// eslint-disable-next-line no-case-declarations
const search = new URLSearchParams(window.location.search)
// eslint-disable-next-line no-case-declarations
let redirect = window.location.pathname
if (search.toString() !== '') {
redirect = window.location.pathname + '?=' + search.toString()
}
window.location.href = `/login?redirect=${encodeURIComponent(redirect)}`
return Promise.reject(new Error('to login'))
default:
message.error(result.message ?? '请求失败')
return Promise.reject(response)
}
}, (error) => {
// console.log('error', error)
const { response } = error
if (response) {
switch (response.status) {
case 401:
if (window.location.pathname === '/login') {
return
}
setToken('')
// 401: 未登录
message.error('登录失败,跳转重新登录')
// eslint-disable-next-line no-case-declarations
const search = new URLSearchParams(window.location.search)
// eslint-disable-next-line no-case-declarations
let redirect = window.location.pathname
if (search.toString() !== '') {
redirect = window.location.pathname + '?=' + search.toString()
}
window.location.href = `/login?redirect=${encodeURIComponent(redirect)}`
return
default:
message.error(response.data.message)
return Promise.reject(response)
}
}
return Promise.reject(error)
})
//创建返回IApiResult类型的request
export const createFetchMethods = () => {
const methods = {}
for (const method of Object.keys(axiosInstance)) {
methods[method] = async <T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>) => {
config = config ?? {}
config.url = url
config.method = method
const isGet = method === 'get'
if (isGet) {
config.params = data
} else {
config.data = data
}
return axiosInstance(config)
.then((response: AxiosResponse<IApiResult<T>>) => {
if (response.data.code !== 200) {
throw new Error(response.data.message)
}
return response.data as IApiResult<T>
})
.catch((err) => {
throw err
})
const methods = {}
for (const method of Object.keys(axiosInstance)) {
methods[method] = async <T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>) => {
config = config ?? {}
config.url = url
config.method = method
const isGet = method === 'get'
if (isGet) {
config.params = data
} else {
config.data = data
}
return axiosInstance(config)
.then((response: AxiosResponse<IApiResult<T>>) => {
if (response.data.code !== 200) {
throw new Error(response.data.message)
}
return response.data as IApiResult<T>
})
.catch((err) => {
throw err
})
}
}
}
return methods as Record<keyof RequestMethods, FetchMethod>
return methods as Record<keyof RequestMethods, FetchMethod>
}
export const request = createFetchMethods()

415
src/routes.tsx

@ -13,85 +13,87 @@ import { Route as LoginRouteImport } from '@/pages/login'
import { generateUUID } from '@/utils/uuid.ts'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import {
AnyRoute,
createRootRouteWithContext,
createRoute,
createRouter, lazyRouteComponent,
Outlet,
redirect,
RouterProvider,
AnyRoute,
createRootRouteWithContext,
createRoute,
createRouter, lazyRouteComponent,
Outlet,
redirect,
RouterProvider,
} from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
import { memo } from 'react'
import { memo, useEffect, useRef } from 'react'
import RootLayout from './layout/RootLayout'
import { IRootContext, MenuItem } from './global'
import { DevTools } from 'jotai-devtools'
import { useAtomValue } from 'jotai'
import { userMenuDataAtom } from '@/store/user.ts'
const PageRootLayout = () => {
return <PageStoreProvider>
<RootLayout/>
</PageStoreProvider>
return <PageStoreProvider>
<RootLayout/>
</PageStoreProvider>
}
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
defaultOptions: {
queries: {
retry: false,
}
}
}
})
const rootRoute = createRootRouteWithContext<IRootContext>()({
component: () => (
<>
<FetchLoading/>
<Outlet/>
<DevTools/>
<TanStackRouterDevtools position={'bottom-right'}/>
</>
),
beforeLoad: ({ location }) => {
if (location.pathname === '/') {
return redirect({ to: '/dashboard' })
}
},
loader: () => {
component: () => (
<>
<FetchLoading/>
<Outlet/>
<DevTools/>
<TanStackRouterDevtools position={'bottom-right'}/>
</>
),
beforeLoad: ({ location }) => {
if (location.pathname === '/') {
return redirect({ to: '/dashboard' })
}
},
loader: () => {
},
notFoundComponent: NotFound,
pendingComponent: PageLoading,
errorComponent: ({ error }) => <ErrorPage error={error}/>,
},
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 authRoute = AuthenticatedImport.update({
getParentRoute: () => rootRoute,
id: '/_authenticated',
getParentRoute: () => rootRoute,
id: '/_authenticated',
} as any)
const layoutNormalRoute = createRoute({
getParentRoute: () => rootRoute,
id: '/_normal_layout',
component: PageRootLayout,
getParentRoute: () => rootRoute,
id: '/_normal_layout',
component: PageRootLayout,
})
const layoutAuthRoute = createRoute({
getParentRoute: () => authRoute,
id: '/_auth_layout',
component: PageRootLayout,
getParentRoute: () => authRoute,
id: '/_auth_layout',
component: PageRootLayout,
})
const notAuthRoute = createRoute({
getParentRoute: () => layoutNormalRoute,
path: '/not-auth',
component: NotPermission
getParentRoute: () => layoutNormalRoute,
path: '/not-auth',
component: NotPermission
})
// const dashboardRoute = DashboardImport.update({
@ -100,8 +102,8 @@ const notAuthRoute = createRoute({
// } as any)
const loginRoute = LoginRouteImport.update({
path: '/login',
getParentRoute: () => emptyRoute,
path: '/login',
getParentRoute: () => emptyRoute,
} as any)
//
@ -127,170 +129,189 @@ const loginRoute = LoginRouteImport.update({
declare module '@tanstack/react-router' {
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
},
}
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 generateDynamicRoutes = (menuData: MenuItem[], parentRoute: AnyRoute) => {
// 递归生成路由,如果有routes则递归生成子路由
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'
if (isLayout && (!menu.path || !menu.component)) {
//没有component的layout,直接返回
return createRoute({
getParentRoute: () => layoutAuthRoute,
id: `/layout-no-path-${generateUUID()}`,
component: EmptyLayout,
})
}
// 递归生成路由,如果有routes则递归生成子路由
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'
if (isLayout && (!menu.path || !menu.component)) {
//没有component的layout,直接返回
return createRoute({
getParentRoute: () => layoutAuthRoute,
id: `/layout-no-path-${generateUUID()}`,
component: EmptyLayout,
})
}
// @ts-ignore 添加menu属性,方便后面获取
const options = {
getParentRoute: () => parentRoute,
menu,
} as any
if (isLayout) {
options.id = path ?? `/layout-${generateUUID()}`
} else {
if (!path) {
console.log(`${menu.name}没有设置视图`)
} else {
options.path = path
}
}
// @ts-ignore 添加menu属性,方便后面获取
const options = {
getParentRoute: () => parentRoute,
menu,
} as any
if (isLayout) {
options.id = path ?? `/layout-${generateUUID()}`
} else {
if (!path) {
console.log(`${menu.name}没有设置视图`)
} else {
options.path = path
}
}
let component = menu.component
// menu.type
// 1,组件(页面),2,IFrame,3,外链接,4,按钮
if (menu.type === 'iframe') {
component = '@/components/Iframe'
}
let component = menu.component
// menu.type
// 1,组件(页面),2,IFrame,3,外链接,4,按钮
if (menu.type === 'iframe') {
component = '@/components/Iframe'
}
//处理component路径
component = component.replace(/^\/pages/, '')
component = component.replace(/^\//, '')
//处理component路径
component = component.replace(/^\/pages/, '')
component = component.replace(/^\//, '')
return createRoute({
...options,
component: lazyRouteComponent(() => import(`./pages/${component}`)),
notFoundComponent: NotFound,
})
}
// 对menuData递归生成路由,只处理type =1 的菜单
const did = (menus: MenuItem[], parentRoute: AnyRoute) => {
return menus.filter((item) => item.type === 'menu').map((item, index) => {
// 如果有children则递归生成子路由,同样只处理type =1 的菜单
const route = generateRoutes(item, parentRoute)
// console.log(route)
if (item.children && item.children.length > 0) {
const children = did(item.children, route)
if (children.length > 0) {
route.addChildren(children)
}
}
route.init({ originalIndex: index })
return route
})
}
return createRoute({
...options,
component: lazyRouteComponent(() => import(`./pages/${component}`)),
notFoundComponent: NotFound,
})
}
const routes = did(menuData, parentRoute)
// 对menuData递归生成路由,只处理type =1 的菜单
const did = (menus: MenuItem[], parentRoute: AnyRoute) => {
return menus.filter((item) => item.type === 'menu').map((item, index) => {
// 如果有children则递归生成子路由,同样只处理type =1 的菜单
const route = generateRoutes(item, parentRoute)
// console.log(route)
if (item.children && item.children.length > 0) {
const children = did(item.children, route)
if (children.length > 0) {
route.addChildren(children)
}
}
route.init({ originalIndex: index })
return route
})
}
const routes = did(menuData, parentRoute)
parentRoute.addChildren(routes)
parentRoute.addChildren(routes)
}
const routeTree = rootRoute.addChildren(
[
//非Layout
loginRoute,
emptyRoute,
//不带权限Layout
layoutNormalRoute.addChildren([
notAuthRoute,
]),
//带权限Layout
// dashboardRoute,
authRoute.addChildren(
[
layoutAuthRoute
/*.addChildren(
[
menusRoute,
departmentsRoute,
usersRoute,
rolesRoute,
]
),*/
]),
]
[
//非Layout
loginRoute,
emptyRoute,
//不带权限Layout
layoutNormalRoute.addChildren([
notAuthRoute,
]),
//带权限Layout
// dashboardRoute,
authRoute.addChildren(
[
layoutAuthRoute
/*.addChildren(
[
menusRoute,
departmentsRoute,
usersRoute,
rolesRoute,
]
),*/
]),
]
)
export const RootProvider = memo((props: { context: Partial<IRootContext> }) => {
generateDynamicRoutes(props.context.menuData ?? [], layoutAuthRoute)
const { data: menuData, isLoading, refetch } = useAtomValue(userMenuDataAtom)
const isFetchRef = useRef(false)
useEffect(() => {
if (isFetchRef.current) {
return
}
isFetchRef.current = true
refetch()
}, [])
if (isLoading) {
return <PageLoading/>
}
generateDynamicRoutes(menuData ?? [], layoutAuthRoute)
const router = createRouter({
routeTree,
context: { queryClient, menuData: [] },
defaultPreload: 'intent',
defaultPendingComponent: () => <Loading loading={true} delay={300}/>
})
const router = createRouter({
routeTree,
context: { queryClient, menuData: [] },
defaultPreload: 'intent',
defaultPendingComponent: () => <Loading loading={true} delay={300}/>
})
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} context={{ ...props.context, queryClient }}/>
</QueryClientProvider>
)
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} context={{ ...props.context, menuData, queryClient }}/>
</QueryClientProvider>
)
})
Loading…
Cancel
Save