超详细!20分钟开发一个React的后台管理系统!

13,951 阅读6分钟

1.项目演示

该项目是一个基础的 React 应用,功能简洁明了,涵盖了用户登录、退出以及数据的增删改查等操作,非常适合初学者学习。文章后面会给出完整的代码。

1.1 登录

1.2 后台首页

1.3 博客列表

1.4 新增编辑博客

1.5 删除博客

2.项目搭建

开发工具:Vscode

2.1 项目技术栈

  • React 脚手架: create-react-app
  • React Hooks
  • UI 组件库:antd
  • 网络请求:axios
  • 路由:react-router-dom
  • 状态管理:zustand

2.2 新建 React 项目

npx create-react-app zhifou-react

然后使用 vscode 打开该项目

在控制台输入 npm start 运行项目

2.3 项目目录

1.在 src 文件夹下,我们先删除多余的文件,只保留 App.js、index.js、index.css。

2.删除 index.js 文件里面 React.StrictMode 标签 和 reportWebVitals() 函数。

3.在 src 文件夹下分别新建 api、assets、router、store、utils 文件夹

  • public: 公共文件,包含项目的入口 HTML 文件和图标。
  • src:源代码目录。
  • App.js: 根组件
  • index.css: 全局 CSS 样式文件
  • index.js: 应用的入口 JS 文件。
  • package.json: 项目的配置文件,定义项目的依赖关系、安装插件的版本
  • api:后端接口
  • assets:图片等静态资源
  • pages:存放 jsx 文件,也就是页面
  • router:路由配置
  • store: 状态管理
  • utils: 工具类

3.安装配置 Antd

Ant Design 官网:

https://ant-design.antgroup.com/components/overview-cn/

安装 Antd

npm install antd --save

4.安装配置路由

npm install  react-router-dom

4.1 在 router 文件夹下新建 index.js

React.lazy 表示懒加载页面组件

4.2 在项目 index.js 里面配置路由

5.安装 zustand

npm install zustand

在 store 文件夹下新建 useLoginStore.js

该 store 主要用来发起用户登录请求、存储 token 和 用户信息、用户退出登录操作。

token.js 主要用来处理本地缓存的 token 信息:

6.安装配置 axios

6.1 安装配置 axios

npm i axios -- save

6.2 封装 http 请求

6.3 封装后台请求接口

7.用户登录

在 pages 文件夹下新建 Login.jsx 文件

因为有了 React Hooks 的加持,大部分人都习惯于使用函数组件开发页面。

登录页面主要用到了 Antd 的 Card、Button、Form、Input、Input 组件

其中 Form 组件的 onFinish 属性绑定登录事件:

onFinish 函数接收的参数就是你在 Form 表单输入的账号和密码

这里我们使用了 zustand 发起网络请求

在 zustand 里面请求正常就存储 token 和 用户信息,如果请求异常就抛出异常。

在 Login.jsx 里面,请求正常就跳转后台主页,否则捕获异常,弹出异常信息。

8.后台主页

在 home 文件夹下新建 index.jsx

后台主页主要用到了 Antd 的 Layout 组件

引入该组件后,需要单独设置样式,才能完全填充整个浏览器页面。

1.新建 index.css 文件

2.导入样式

8.1 收起菜单栏

collapsed 参数用来控制展开和收起菜单操作,这里我们使用 useState 存储 collapsed 的值。

const [collapsed, setCollapsed] = useState(false);

useState 相当于 Vue 中的 data。

因为 React 中沒有 vue 的 v-if 等指令,所以这里使用三元操作符显示展开和收缩时左上角的样式。

8.2 菜单栏

菜单栏主要使用了 Antd 的 Menu 组件,defaultSelectedKeys 表示初始选中的菜单项,selectedKeys 表示当前选中的菜单项

其中 useLocation 的 pathname 表示当前路由的地址。

8.3 显示主要内容

这里使用 Outlet 标签渲染组件。

8.4 用户登录判断

我们都知道‌ React 中的 useEffect 是一个 Hook,可以在函数组件中处理副作用。依赖数组为空数组,组件只在首次渲染时执行一次

这里在后台页面首次渲染时判断缓存中是否有用户信息,如果没有则跳转到登录页面。

注:在真实的项目中应该使用路由导航守卫管理路由权限,后续文章会详细讲解 React 的路由导航守卫,这里不再赘述。

9.博客列表

在 blog 文件夹下新建 index.jsx 文件。

博客列表页面主要使用了 Antd 的 Table 组件

9.1 搭建表结构

1.dataSource 绑定列表数据

// 博客列表数据
const [blogList, setBlogList] = useState([]);
// 博客总数
const [total, setTotal] = useState(0);

2.columns 绑定表格列的配置描述。

  // 表格参数的控制
  const columns = [
    {
      title: "序号",
      dataIndex: "id",
      width: 100,
      render: (_, record, index) => (index + 1).toString(),
    },
    {
      title: "标题",
      dataIndex: "title",
      key: "title",
    },
    {
      title: "创建时间",
      key: "createTime",
      render: (record) => moment(record.createTime).format("YYYY-MM-DD"),
    },
    {
      title: "操作",
      key: "action",
      render: (row) => (
        <span>
          <Button
            style={{
              backgroundColor: "orange",
              color: "white",
              marginRight: "5px",
            }}
            onClick={() => handleOpenUpdateModal(row)}
          >
            编辑
          </Button>
        </span>
      ),
    },
  ];

当然你也可以按照原来习惯的方式搭建表格框架,例如:

9.2 分页

  • current:当前页
  • pageSize:每页显示个数
  • total:总数
  • showSizeChanger:展示切换个数操作
  • showQuickJumper:展示跳转页码操作
  • showTotal:显示总数
  • onChange:绑定切换每页个数和跳转页码的事件

引入分页后默认显示英文,这里我们需要设置成中文:

主要就是使用 ConfigProvider 包裹 Table 组件

9.3 获取列表数据

我们在 useEffect 里面获取博客列表数据,当查询参数变化时,就执行查询列表操作

当切换每页显示个数或者跳转页码时,就改变查询参数的值:

  // 切换每页页码、跳转页码
  const handlePaginationChange = (newPageNum, newPageSize) => {
    setParams({
      ...params,
      current: newPageNum,
      size: newPageSize,
    });
  };

10.新增和编辑博客

新增和编辑博客共用一个页面,这里主要用到了 Antd 的 Modal 对话框和 Form 组件。

<Modal title={isUpdate ? "编辑博客" : "新增博客"}
        open={isModalOpen}
        onCancel={() => setIsModalOpen(false)}
        footer={[]}
      >
        <Form
          form={form}
          style={{ maxWidth: 400 }}
          initialValues={{ remember: true }}
          onFinish={handleAddUpdateBlog}
          autoComplete="off"
        >
          <Form.Item name="id" hidden>
            <Input />
          </Form.Item>
          <Form.Item
            label="标题"
            name="title"
            rules={[{ required: true, message: "请输入标题" }]}
          >
            <Input placeholder="请输入标题" />
          </Form.Item>
          <Form.Item
            label="类别"
            name="type"
            rules={[{ required: true, message: "请选择类别" }]}
          >
            <Radio.Group>
              <Radio value={"java"}> java </Radio>
              <Radio value={"vue"}> vue </Radio>
              <Radio value={"react"}> react </Radio>
              <Radio value={"uniapp"}> uniapp </Radio>
              <Radio value={"js"}> js </Radio>
            </Radio.Group>
          </Form.Item>
          <Form.Item
            label="内容"
            name="content"
            rules={[{ required: true, message: "请输入内容" }]}
          >
            <TextArea
              placeholder="请输入内容"
              autoSize={{
                minRows: 4,
                maxRows: 6,
              }}
            />
          </Form.Item>
          <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
            <Space>
              <Button type="primary" htmlType="submit">
                提交
              </Button>
              <Button htmlType="submit" onClick={() => setIsModalOpen(false)}>
                取消
              </Button>
            </Space>
          </Form.Item>
        </Form>
      </Modal>

其中对话框的 open 属性用来表示对话框是否可见,对话框的标题通过 isUpdate 属性动态显示。

  // 判断是否更新
  const [isUpdate, setIsUpdate] = useState(false);
  // 模态框打开
  const [isModalOpen, setIsModalOpen] = useState(false);

10.1 新增按钮事件

// 打开新增对话框
const handleOpenAddModal = () => {
  form.resetFields(); //每次打开新增窗口时都要清空数据
  setIsUpdate(false);
  setIsModalOpen(true);
};

10.2 编辑按钮事件

  const handleOpenUpdateModal = (row) => {
    //将当前行的数据赋值给表单
    form.setFieldsValue(row);
    setIsUpdate(true);
    setIsModalOpen(true);
  };

10.3 调用后台接口

  // 新增和编辑博客
  const handleAddUpdateBlog = async (blogForm) => {
    const { data } = await blogApi.saveBlog(blogForm);
    if (data.code == 200) {
      form.resetFields();
      setIsModalOpen(false);
      getBlogList();
    } else {
      message.error(data.message);
    }
  };

11.删除博客

删除博客主要用到了 Antd 的 Popover 组件

 <Popover
  title="您确定要删除该博客吗?"
  content={
    <Button
      onClick={() => handleDeleteBlog(row.id)}
      type="primary"
      danger
    >
      确定
    </Button>
  }
>
  <Button type="primary" danger>
    删除
  </Button>
</Popover>

调用后台删除接口:

  // 删除博客
  const handleDeleteBlog = async (id) => {
    await blogApi.delBlog(id);
    message.success("删除成功");
    getBlogList();
  };

12.退出登录

后台主页右上角的用户信息主要用到了 Antd 的 Dropdown 组件

  const [userInfo, setUserInfo] = useState(
    JSON.parse(localStorage.getItem("zhifou-user"))
  );

onClick 方法绑定退出登录事件:

  const { userLogout } = useLoginStore();
  // 退出
  const onClick = ({ key }) => {
    userLogout();
    navigate("/login");
  };

13.完整代码

https://gitee.com/zhifou-tech/zhifou-react

拿到代码之后

  • 1.npm install 安装依赖
  • 2.npm start 运行项目
  • 3.登录账号:zhifou、libai、houyi、liyuanfang、harmony
  • 4.登录密码:123456