创建一个react项目(router,store,axios,antd)最后有项目地址

发布于:2024-05-10 ⋅ 阅读:(16) ⋅ 点赞:(0)

第一步:使用cra脚手架 创建项目

文档地址:Create React App 中文文档

npx create-react-app '你的项目名称'

第二步:整理项目结构和删除多余代码

目标效果图:

在src目录下分别新建apis,assets,components,pages,router,store,utlis文件夹

原始项目目录结构只保留App.js,index.css,index.js这个三个文件

App.js:

index.css:

index.js:

拓展:安装scss

npm install sass -D

测试和使用:

App.js内容:

把index.css改为index.scss

index.js内容:

效果:

 第三步:安装ui组件库-Ant Design

官网链接:在 create-react-app 中使用 - Ant Design

npm install antd --save

测试使用:

src/App.js:

import { Button } from 'antd';
function App() {
  return (
    <div>
      <Button type="primary">Button</Button>
    </div>
  );
}

export default App;

效果图:

第四步:配置路由

中文文档:在开始之前 | React Router6 中文文档

第一步:安装

npm install react-router-dom

第二步:新建两个页面文件

在src/pages下新建两个文件Layout和Login

Layout/index.js:

const Layout= ()=>{
    return(
        <div>
            我是Layout
        </div>
    )
}

export default Layout

Login/index.js:

const Login= ()=>{
    return(
        <div>
            我是Login
        </div>
    )
}

export default Login

第三步:新建路由实例

 src/router下新建index.js

import Layout from "../pages/Layout";
import Login from "../pages/Login";
import { createBrowserRouter } from "react-router-dom";
// 配置路由
const router = createBrowserRouter([
    {
        path: '/',
        element: <Layout></Layout>
    },
    {
        path: '/login',
        element: <Login></Login>
    }
])

export default router

第四步:使用挂载实例

在src/index.js种导入

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.scss';
import router from './router';
import { RouterProvider } from 'react-router-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <RouterProvider router={router}></RouterProvider>
  </React.StrictMode>
);

效果图:

第五步:封装request

第一步:修改src/pages/Login/index.js文件

import { Card, Button, Form, Input, message } from 'antd';
import './index.scss'
import { useDispatch } from 'react-redux';
import { fetchLogin } from '../../store/modules/user';
import { useNavigate } from 'react-router-dom';
const Login = () => {
    const navigate= useNavigate()
    // 实例的账号密码
    // 13800000002
    // 246810
    const dispatch = useDispatch()
    const onFinish =async (values) => {
        await dispatch(fetchLogin(values))
        // 跳转
        navigate('/')
        // 提示
        message.success('登录成功')
        console.log('Success:', values);
      };
      const onFinishFailed = (errorInfo) => {
        console.log('Failed:', errorInfo);
      };
    return (
        <div>
            <Card className='card'>
                <Form
                    name="basic"
                    labelCol={{
                        span: 8,
                    }}
                    wrapperCol={{
                        span: 16,
                    }}
                    style={{
                        maxWidth: 600,
                    }}
                    initialValues={{
                        remember: true,
                    }}
                    onFinish={onFinish}
                    onFinishFailed={onFinishFailed}
                    autoComplete="off"
                >
                    <Form.Item
                        label="Username"
                        name="mobile"
                        rules={[
                            {
                                required: true,
                                message: 'Please input your username!',
                            },
                        ]}
                    >
                        <Input />
                    </Form.Item>

                    <Form.Item
                        label="Password"
                        name="code"
                        rules={[
                            {
                                required: true,
                                message: 'Please input your password!',
                            },
                        ]}
                    >
                        <Input.Password />
                    </Form.Item>

                    <Form.Item
                        wrapperCol={{
                            offset: 8,
                            span: 16,
                        }}
                    >
                        <Button type="primary" htmlType="submit">
                            Submit
                        </Button>
                    </Form.Item>
                </Form>
            </Card>
        </div>
    )
}

export default Login

在src/pages/Login下添加index.scss

.card{
    display: flex;
    justify-content: center;
    position: fixed;
    top: 100px;
    left:35%;
    width: 500px;
}

第二步:封装request

中文文档起步 | Axios中文文档 | Axios中文网

安装axios

npm install axios

在src/utlis下新建request.js和index.js

request.js:

// axios封装
// 1 根域名配置
// 2 超时时间
// 3 请求拦截器
// 4 响应拦截器
import axios from "axios";
import { getToken } from "./token";
const request = axios.create({
    baseURL:'http://geek.itheima.net/v1_0',
    timeout:5000
})

// 添加请求拦截器
request.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    const token= getToken()
    if(token){
        config.headers.Authorization='Bearer '+token
    }
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
request.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response.data;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
  });

export {request}

index.js:

// 统一中转工具函数导出
import { request } from "./request";
import { setToken,getToken,removeToken } from "./token";
export {
    request,
    setToken,
    getToken,
    removeToken
}

第六步:利用redux封装token

第一步安装react-redux/@reduxjs/toolkit

中文文档:入门 Redux | Redux 中文官网

npm install @reduxjs/toolkit react-redux

 在src/store新建modules/user.js和index.js

src/store/index.js:

// 组合子模块
import { configureStore } from "@reduxjs/toolkit";
import userStore from "./modules/user";
const store = configureStore({
    reducer:{
        userStore
    }
})

export default store

src/store/modules/user.js:

// 放置和用户相关的
import { createSlice } from "@reduxjs/toolkit";
import { request,setToken as _setToken,getToken } from "../../utils";
const userSlice= createSlice({
    name:'user',
    initialState:{
        // token持久化
        token:getToken()||'',
    },
    reducers:{
        setToken:(state,action)=>{
            state.token=action.payload
            // token持久化
            _setToken(action.payload)
        }
    }

})

// 解构
const {setToken} = userSlice.actions

// 获取reducer函数
const userStore= userSlice.reducer

// 封装一部方法 完成登录 获取token
const fetchLogin=(loginForm)=>{
    return async (dispatch)=>{
        // 1 发送异步请求
        const res = await request.post('/authorizations',loginForm)
        // 2 提交同步修改方法
        dispatch(setToken(res.data.token))

    }
}

export {fetchLogin,setToken}

export default userStore

最后在src/index.js添加store

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.scss';
import router from './router';
import { RouterProvider } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <RouterProvider router={router}></RouterProvider>
    </Provider>
  </React.StrictMode>
);

 然后在login进行调用

封装存,取,删token的方法

在src/utlis新建token.js

const TOKENKEY='token'
const setToken =(token)=>{
    localStorage.setItem(TOKENKEY,token)
}

const getToken=()=>{
   return localStorage.getItem(TOKENKEY)
}

const removeToken=()=>{
    localStorage.removeItem(TOKENKEY)
}

export {setToken,getToken,removeToken}

在src/pages/Layout下进行测试

// 测试toekn
import { useEffect } from "react"
import { request } from "../../utils"
const Layout= ()=>{
    useEffect(()=>{
        request.get('/user/profile')
    },[])
    return(
        <div>
            我是Layout
        </div>
    )
}

export default Layout

 第七步:根据token进行路由权限判断

在src/components下新建Auth.js

// 封装高阶组件
// 有token正常访问 无token跳转login

import { getToken } from "../utils"
import { Navigate } from "react-router-dom"

const Auth = ({children}) => {
    const token = getToken()
    if(token){
        return <>{children}</>
    }else{
        return <Navigate to={'/login'} replace></Navigate>
    }
}

export default Auth

 然后在router/index.js修改

import Layout from "../pages/Layout";
import Login from "../pages/Login";
import { createBrowserRouter } from "react-router-dom";
import Auth from "../components/Auth";
import Home from "../pages/Home";
import Article from "../pages/Article";
import Publish from "../pages/Publish";
// 配置路由
const router = createBrowserRouter([
    {
        path: '/',
        element: <Auth><Layout></Layout></Auth>,
        children:[
            {
                index:true,
                element:<Home></Home>
            },
            {
                path:'article',
                element:<Article></Article>
            },            {
                path:'publish',
                element:<Publish></Publish>
            }
        ]
    },
    {
        path: '/login',
        element: <Login></Login>
    }
])

export default router

新建src/pages下的Home,Article,Publish文件

 Home.js:

const Home = ()=>{
    return(
        <div>我是Home</div>
    )
}
export default Home

下面两个文件类推!!!

 第八步:封装Laout样式组件

覆盖src/pages/Layout/index.js

import React, { useEffect, useState } from 'react';
import {
    MenuFoldOutlined,
    MenuUnfoldOutlined,
    UploadOutlined,
    UserOutlined,
    VideoCameraOutlined,
} from '@ant-design/icons';
import { Button, Layout, Menu, theme, Popconfirm } from 'antd';
import { Outlet, useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { clearUserInfo, fetchUserInfo } from '../../store/modules/user';
const { Header, Sider, Content } = Layout;
const App = () => {
    const navigste = useNavigate()
    const dispatch = useDispatch()
    const [collapsed, setCollapsed] = useState(false);
    const {
        token: { colorBgContainer, borderRadiusLG },
    } = theme.useToken();

    // 侧边跳转
    const MenuClick = (key) => {
        navigste(key)
    }

    useEffect(() => {
        dispatch(fetchUserInfo())
    }, [dispatch])

    const userInfo = useSelector(state => state.userStore.userInfo)

    // 退出按钮
    const confirm=()=>{
        dispatch(clearUserInfo())
        navigste('/login')
    }
    return (
        <Layout>
            <Sider trigger={null} collapsible collapsed={collapsed}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                    <img src='https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg' style={{ width: '50%', height: '50px' }} alt=''></img>
                    {!collapsed &&
                        <div style={{ color: '#fff', width: '50%' }}>哈哈哈</div>
                    }
                </div>
                <Menu
                    onClick={({ key }) => MenuClick(key)}
                    theme="dark"
                    mode="inline"
                    defaultSelectedKeys={['/']}
                    items={[
                        {
                            key: '/',
                            icon: <UserOutlined />,
                            label: '首页',
                        },
                        {
                            key: '/article',
                            icon: <VideoCameraOutlined />,
                            label: '文章',
                        },
                        {
                            key: '/publish',
                            icon: <UploadOutlined />,
                            label: '发布',
                        },
                    ]}
                />
            </Sider>
            <Layout>
                <Header
                    style={{
                        padding: '0 24px 0 0',
                        background: colorBgContainer,
                        display: 'flex',
                        justifyContent: 'space-between'
                    }}
                >
                    <Button
                        type="text"
                        icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
                        onClick={() => setCollapsed(!collapsed)}
                        style={{
                            fontSize: '16px',
                            width: 64,
                            height: 64,
                        }}
                    />
                    <div style={{ display: 'flex', alignItems: 'center', fontSize: '16px' }}>
                        <span>{userInfo.name}</span>
                        <UserOutlined style={{ marginLeft: '10px' }} />
                        <Popconfirm
                            title="退出"
                            description="确认退出吗?"
                            onConfirm={confirm}
                            okText="Yes"
                            cancelText="No"
                        >
                            <span style={{ cursor: "pointer" }}>退出</span>

                        </Popconfirm>
                    </div>
                </Header>
                <Content
                    style={{
                        margin: '24px 16px',
                        padding: 24,
                        minHeight: 280,
                        background: colorBgContainer,
                        borderRadius: borderRadiusLG,
                    }}
                >
                    {/* 二级路由出口 */}
                    <Outlet></Outlet>
                </Content>
            </Layout>
        </Layout>
    );
};
export default App;

封装退出方法

src/store/modules/user.js

// 放置和用户相关的
import { createSlice } from "@reduxjs/toolkit";
import { request,setToken as _setToken,getToken, removeToken } from "../../utils";
const userSlice= createSlice({
    name:'user',
    initialState:{
        // token持久化
        token:getToken()||'',
        userInfo:{}
    },
    reducers:{
        setToken:(state,action)=>{
            state.token=action.payload
            // token持久化
            _setToken(action.payload)
        },
        setUserInfo:(state,action)=>{
            state.userInfo=action.payload
        },
        clearUserInfo:(state)=>{
            state.token=''
            state.userInfo={}
            removeToken()
        }
    }

})

// 解构
const {setToken,setUserInfo,clearUserInfo} = userSlice.actions

// 获取reducer函数
const userStore= userSlice.reducer

// 封装异步方法 完成登录 获取token
const fetchLogin=(loginForm)=>{
    return async (dispatch)=>{
        // 1 发送异步请求
        const res = await request.post('/authorizations',loginForm)
        // 2 提交同步修改方法
        dispatch(setToken(res.data.token))

    }
}
// 获取个人信息
const fetchUserInfo=()=>{
    return async (dispatch)=>{
        // 1 发送异步请求
        const res = await request.get('/user/profile')
        // 2 提交同步修改方法
        dispatch(setUserInfo(res.data))
    }
}

export {fetchLogin,setToken,fetchUserInfo,clearUserInfo}

export default userStore

 第九步:处理token失效

src/utlis/request.js

// axios封装
// 1 根域名配置
// 2 超时时间
// 3 请求拦截器
// 4 响应拦截器
import axios from "axios";
import { getToken, removeToken } from "./token";
import router from "../router";
const request = axios.create({
    baseURL:'http://geek.itheima.net/v1_0',
    timeout:5000
})

// 添加请求拦截器
request.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    const token= getToken()
    if(token){
        config.headers.Authorization='Bearer '+token
    }
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
request.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response.data;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    // 处理token失效
    if(error.response.status===401){
        removeToken()
        router.navigate('/login')
        window.location.reload()
    }
    return Promise.reject(error);
  });

export {request}

第十步:封装Api请求

src/apis/user.js

import { request } from "../utils";

const loginApi=(data)=>{
    return request({
        url:'/authorizations',
        method:'post',
        data
    })
}

const userInfoApi=()=>{
    return request({
        url:'/user/profile',
        method:'get',
    })
}

export {loginApi,userInfoApi}

修改src/store/modules/user.js

// 放置和用户相关的
import { createSlice } from "@reduxjs/toolkit";
import {setToken as _setToken,getToken, removeToken } from "../../utils";
import { loginApi, userInfoApi } from "../../apis/user";
const userSlice= createSlice({
    name:'user',
    initialState:{
        // token持久化
        token:getToken()||'',
        userInfo:{}
    },
    reducers:{
        setToken:(state,action)=>{
            state.token=action.payload
            // token持久化
            _setToken(action.payload)
        },
        setUserInfo:(state,action)=>{
            state.userInfo=action.payload
        },
        clearUserInfo:(state)=>{
            state.token=''
            state.userInfo={}
            removeToken()
        }
    }

})

// 解构
const {setToken,setUserInfo,clearUserInfo} = userSlice.actions

// 获取reducer函数
const userStore= userSlice.reducer

// 封装异步方法 完成登录 获取token
const fetchLogin=(loginForm)=>{
    return async (dispatch)=>{
        // 1 发送异步请求
        const res = await loginApi(loginForm)
        // 2 提交同步修改方法
        dispatch(setToken(res.data.token))

    }
}
// 获取个人信息
const fetchUserInfo=()=>{
    return async (dispatch)=>{
        // 1 发送异步请求
        const res = await userInfoApi()
        // 2 提交同步修改方法
        dispatch(setUserInfo(res.data))
    }
}

export {fetchLogin,setToken,fetchUserInfo,clearUserInfo}

export default userStore

到这里恭喜你完成了一个简单版本的react后台模板

示例代码地址:react-demo-pc: 练习使用的一个react后台模板,主要熟悉联系ui组件库,redux,router,axios