react 配合vite,使用 import.meta.glob,实现动态路由
1. 安装必要的库
npm init vite@latest my-react-app --template react
cd my-react-app
npm install
npm install react-router-dom axios
npm install @reduxjs/toolkit react-redux
2. 定义后端接口调用
`// src/api.js
import axios from 'axios';
const api = axios.create({
baseURL: 'https://your-api-base-url.com',
});
export const login = async (username, password) => {
const response = await api.post('/login', { username, password });
return response.data;
};
export const getMenuData = async () => {
const response = await api.get('/menu');
return response.data;
};
3. 创建 Redux Store
使用 Redux Toolkit 来创建一个 Redux Store:
// src/store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';
const authSlice = createSlice({
name: 'auth',
initialState: {
isAuthenticated: false,
menu: [],
},
reducers: {
login: (state, action) => {
state.isAuthenticated = true;
state.menu = action.payload.menu;
},
logout: (state) => {
state.isAuthenticated = false;
state.menu = [];
},
},
});
export const { login, logout } = authSlice.actions;
export default configureStore({
reducer: {
auth: authSlice.reducer,
},
});
4. 用户登录并获取菜单数据
创建一个 Login 组件,用于用户登录和获取菜单数据:
// src/pages/Login.jsx
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { login, getMenuData } from '../api';
import { login as loginAction } from '../store';
const Login = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const dispatch = useDispatch();
const history = useHistory();
const handleLogin = async () => {
try {
await login(username, password);
const menu = await getMenuData();
dispatch(loginAction({ menu }));
history.push('/');
} catch (error) {
console.error('Login failed', error);
}
};
return (
<div>
<h1>Login</h1>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Username"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button onClick={handleLogin}>Login</button>
</div>
);
};
export default Login;
5. 动态生成路由和菜单
使用 import.meta.glob 动态加载组件并生成路由:
// src/App.jsx
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import Login from './pages/Login';
import PrivateRoute from './components/PrivateRoute';
import Menu from './components/Menu';
import { getMenuData } from './api';
import { login as loginAction } from './store';
// 动态导入所有页面组件
const modules = import.meta.glob('./pages/*.jsx');
const App = () => {
const dispatch = useDispatch();
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
const [routes, setRoutes] = useState([]);
useEffect(() => {
const fetchMenuData = async () => {
const menu = await getMenuData();
dispatch(loginAction({ menu }));
const generateRoutes = (menu) => {
return menu.map((item) => {
if (item.children) {
return generateRoutes(item.children);
}
const Component = React.lazy(modules[`./pages/${item.component}.jsx`]);
return {
path: item.path,
exact: item.exact,
component: Component,
};
});
};
setRoutes(generateRoutes(menu));
};
fetchMenuData();
}, [dispatch]);
return (
<Router>
<Menu />
<React.Suspense fallback={<div>Loading...</div>}>
<Switch>
{routes.map((route, index) => (
<PrivateRoute
key={index}
path={route.path}
exact={route.exact}
component={route.component}
/>
))}
<Route path="/login" component={Login} />
<Redirect to="/" />
</Switch>
</React.Suspense>
</Router>
);
};
export default App;
6. 创建菜单组件
// src/components/Menu.jsx
import React from 'react';
import { Link } from 'react-router-dom';
import { useSelector } from 'react-redux';
const Menu = () => {
const menu = useSelector((state) => state.auth.menu);
const generateMenu = (menu) => {
return menu.map((item) => {
if (item.children) {
return (
<div key={item.path}>
<span>{item.title}</span>
<div>{generateMenu(item.children)}</div>
</div>
);
}
return (
<div key={item.path}>
<Link to={item.path}>{item.title}</Link>
</div>
);
});
};
return <div>{generateMenu(menu)}</div>;
};
export default Menu;
7. 创建权限组件
// src/components/PrivateRoute.jsx
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { useSelector } from 'react-redux';
const PrivateRoute = ({ component: Component, ...rest }) => {
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
return (
<Route
{...rest}
render={(props) =>
isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
};
export default PrivateRoute;
8. 创建入口文件
// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';
import './index.css';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
总结
通过以上步骤,我们使用 Vite 作为构建工具,并利用 import.meta.glob 动态导入组件,通过后端接口获取授权菜单数据并在 React 应用中生成动态路由。这样可以根据用户的不同权限进行菜单和路由的动态生成和控制。根据具体需求,可以进一步优化和扩展权限逻辑。