使用vite+react+ts+Ant Design开发后台管理项目(四)

前言

本文将引导开发者从零基础开始,运用vite、react、react-router、react-redux、Ant Design、less、tailwindcss、axios等前沿技术栈,构建一个高效、响应式的后台管理系统。通过详细的步骤和实践指导,文章旨在为开发者揭示如何利用这些技术工具,从项目构思到最终实现的全过程,提供清晰的开发思路和实用的技术应用技巧。

项目gitee地址:lbking666666/enqi-admin

本系列文章:

本章节添加菜单对应的路由页面、菜单的数据使用mock模拟接口,菜单数据的请求

菜单页面

路由页面

在src文件夹下新增pages文件夹,新增三个文件夹home\setting\shop

1.新增首页文件,在home文件夹下新增index.tsx

javascript 复制代码
const Home = ()=>{
    return (
        <div>
        <h1>Home</h1>
    </div>
    )
}
export default Home;

2.新增用户管理和角色管理页面,在setting文件夹下新增role.tsx和user.tsx

javascript 复制代码
//role.tsx
const Role = () => {
  return (<div>Role</div>);
};
export default Role;
javascript 复制代码
//user.tsx
const user = ()=>{
    return(<div>User</div>)
}
export default user;
  1. 新增商品分类、订单管理、商品管理页面,在shop文件夹下新增category.tsx、order.tsx和product.tsx
javascript 复制代码
//category
const Category = ()=>{
    return(
        <div>
            <h1>Category</h1>
        </div>
    )
}
export default Category;
javascript 复制代码
//order.tsx
const Order = ()=>{
    return (
        <div>Order</div>
    )
}
export default Order;
javascript 复制代码
//product.tsx
const Product = ()=>{
    return(
        <div>
            Product
        </div>
    )   
}
export default Product;

路由配置

把新建的路由页面引入到路由文件中并添加一个重定向访问/时候重定向到/home,使用react-router中的Navigte组件

javascript 复制代码
import { createBrowserRouter, Navigate } from "react-router-dom";

import AppLayout from "@/layout/index";
import Home from "@/pages/home";
import Role from "@/pages/setting/role";
import User from "@/pages/setting/user";
import Category from "@/pages/shop/category";
import Product from "@/pages/shop/product";
import Order from "@/pages/shop/order";

const routers = createBrowserRouter([
  {
    path: "/",
    element: <AppLayout />,
    children: [
      {
        path: "/",
        element: <Navigate to="/home" />,
      },
      {
        path: "/home",
        element: <Home />,
      },
      {
        path: "/setting/role",
        element: <Role />,
      },
      {
        path: "/setting/user",
        element: <User />,
      },
      {
        path: "/shop/category",
        element: <Category />,
      },
      {
        path: "/shop/product",
        element: <Product />,
      },
      {
        path: "/shop/order",
        element: <Order />,
      },
    ],
  },
]);

export default routers;

路由呈现

路由配置完成怎么能在游览器中输入不同的路由地址显示当前路由对应的内容呢,这里使用react-router中的Outlet组件。

修改layout文件夹下的main.tsx文件

javascript 复制代码
import { Layout, theme } from "antd";
import {Outlet}  from 'react-router-dom'

const { Content } = Layout;
const AppMain = () => {
  const {
    token: { colorBgContainer, borderRadiusLG },
  } = theme.useToken();
  return (
    <Content
      style={{
        margin: "24px 16px",
        padding: 24,
        minHeight: 280,
        background: colorBgContainer,
        borderRadius: borderRadiusLG,
      }}
    >
      <Outlet />
    </Content>
  );
};
export default AppMain;

菜单数据

这里使用mock来模拟接口数据

引入mock

bash 复制代码
npm i mockjs @types/mockjs vite-plugin-mock -D

配置mock

vite-plugin-mock提供本地和生产模拟服务。

vite 的数据模拟插件,是基于 vite.js 开发的。 并同时支持本地环境和生产环境。 Connect 服务中间件在本地使用,mockjs 在生产环境中使用。

修改vite.config.ts文件

javascript 复制代码
import * as path from 'path';
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { viteMockServe } from 'vite-plugin-mock'
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    viteMockServe({ 
      mockPath: './src/mock', // mock文件夹路径默认是 src/mock
      enable: true, // 默认是 false,可以根据环境变量开启
    }),
  ],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, './src') // 路径别名
    }
  }
})

菜单项属性

在src文件夹下新增types文件夹,在types文件夹下新增menu.d.ts

javascript 复制代码
// 菜单项属性
export interface MenuItemProps {
    id?: string;
    key:string;
    icon?:string;
    label:string;
    children?:MenuItemProps[];
  }

菜单mock数据

在src文件夹下新增mock文件夹

这里的文件夹名称需要和vite配置中viteMockServe的地址一致

新增menu.ts

javascript 复制代码
import Mock from "mockjs";
import { MenuItemProps } from "@/types/menu.d";
// 修正icon的类型问题,因为JSX元素不能作为JSON对象的一部分,这里已经改为字符串
const items:MenuItemProps[] = [
  {
    id: Mock.mock("@id"),
    key: "home",
    icon: "home",
    label: "首页",
  },
  {
    id: Mock.mock("@id"),
    key: "setting",
    icon: "setting",
    label: "系统管理",
    children: [
      {
        key: "user",
        label: "用户管理",
      },
      {
        key: "role",
        label: "角色管理",
      },
    ],
  },
  {
    id: Mock.mock("@id"),
    key: "shop",
    icon: "shop",
    label: "商城管理",
    children: [
      {
        key: "category",
        label: "商品分类",
      },
      {
        key: "product",
        label: "商品管理",
      },
      {
        key: "order",
        label: "订单管理",
      }
    ],
  }
];

export default [
  // 用户登录
  {
    url: "/api/menu",
    method: "GET",
    response: () => {
      return {
        code: 200,
        success: true,
        message: "请求成功。",
        data: items,
      };
    },
  },
];

菜单请求

引入axios

bash 复制代码
npm install axios

封装axios

在src文件夹下新增api文件夹,在api文件夹下新增request.ts文件

javascript 复制代码
//request.ts
import axios, { AxiosRequestConfig } from "axios";
//接口返回数据
export interface ApiRes<T> {
  success: boolean;
  code: number;
  data?: T;
  message: string;
}
const instance = axios.create({
  baseURL: "/api",
  timeout: 5000,
});
// 添加请求拦截器
instance.interceptors.request.use(
  function (config) {
    // 请求成功做点什么
    return config;
  },
  function (error) {
    // 对请求错误做点什么
    return Promise.reject(error);
  }
);
// 添加响应拦截器
instance.interceptors.response.use(
  function (response) {
    // 对响应成功做点什么
    return response.data;
  },
  function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  }
);

export default async <T>(config: AxiosRequestConfig) => {
  const response: ApiRes<T> = await instance(config);
  return response;
};

菜单请求

在api文件夹下新增menu.ts

javascript 复制代码
//menu.ts
import request from './request'

// 获取当前用户信息
export const getMenu = () => {
  return request({
    method: 'GET',
    url: '/menu'
  })
}

菜单完善

菜单组件

在layout文件夹下新增menu.tsx

javascript 复制代码
//layout/menu.tsx
import React from "react";
import { HomeOutlined, SettingOutlined, ShopOutlined } from "@ant-design/icons";
import { Menu } from "antd";
import { MenuItemProps } from "@/types/menu";
import { useNavigate } from "react-router-dom";

// 图标映射
const Icons = {
  home: HomeOutlined,
  setting: SettingOutlined,
  shop: ShopOutlined,
};
// 获取图标组件
const IconByName: React.FC<{ iconName: string }> = ({ iconName }) => {
  // 获取图标组件
  const IconComponent = Icons[iconName as keyof typeof Icons];
  // 返回图标组件
  return IconComponent ? <IconComponent /> : null;
};

// 侧边栏
const AppMenu: React.FC<{ menu: MenuItemProps[] }> = ({ menu }) => {
  const navigate = useNavigate();

  const handleMenu = ({ keyPath }: { keyPath: string[] }) => {
    const routerKey: string = keyPath.reverse().join("/");
    navigate( routerKey );
  };
  const menuData = menu.map((item: MenuItemProps) => {
    return {
      key: item.key,
      label: item.label,
      icon: item.icon ? <IconByName iconName={item.icon} /> : undefined,
      children: item.children?.map((child) => ({
        key: child.key,
        label: child.label,
      })),
    };
  });
  return (
    <Menu onClick={handleMenu} theme="dark" mode="inline" items={menuData} />
  );
};

export default AppMenu;

接口请求

修改layout文件夹下的sider.tsx

javascript 复制代码
import React, { useEffect, useState } from "react";
import { Layout } from "antd";
import { getMenu } from "../api/menu";
import AppMenu from "./menu";
import { MenuItemProps } from "@/types/menu";
const { Sider } = Layout;

// 侧边栏
const AppSider: React.FC<{ collapsed: boolean }> = ({ collapsed }) => {
  // 菜单数据
  const [menu, setMenu] = useState([] as MenuItemProps[]);
  // 获取菜单数据
  useEffect(() => {
    // 获取菜单数据
    const getData = async () => {
      const res = await getMenu();
      const menuData = res?.data as MenuItemProps[];
      // 设置菜单数据
      setMenu([...menuData]);
    };
    getData();
  }, []);
  // 返回侧边栏
  return (
    <Sider trigger={null} collapsible collapsed={collapsed}>
      <div className="demo-logo-vertical" />
      <AppMenu menu={menu} />
    </Sider>
  );
};

export default AppSider;

效果预览

在network中可以看到接口请求成功,菜单渲染出来,点击后可以正常跳转页面

一些说明

1.菜单中点击后路由跳转使用了react-router中的useNavigate

2.请求接口使用useEffect

3.查看接口请求发现请求了两次这里是因为使用了严格模式StrictMode

严格模式启用了以下仅在开发环境下有效的行为:

后续

本篇文章为项目使用axios和mock模拟接口,代码已经同步到了gitee仓库,下一篇丰富具体的页面

相关推荐
懒大王爱吃狼23 分钟前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
小牛itbull1 小时前
ReactPress:重塑内容管理的未来
react.js·github·reactpress
待磨的钝刨1 小时前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
逐·風4 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫5 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦5 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子6 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山6 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享7 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
从兄7 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript