使用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仓库,下一篇丰富具体的页面

相关推荐
猿大师办公助手6 分钟前
如何在Chrome最新浏览器中调用ActiveX控件?
前端·chrome
V_fanglue37058 分钟前
qmt量化交易策略小白学习笔记第67期【qmt编程之获取ETF申赎清单】
大数据·前端·数据库·笔记·python·学习·区块链
林啾啾37 分钟前
vue3实现自定义主题色切换功能
前端·vue.js
墨·殇1 小时前
vue2实现提取字符串数字并修改数字样式(正则表达式)
前端·javascript·vue.js
软糖工程0011 小时前
正则表达式【详细解读】
大数据·前端·爬虫·python·学习·正则表达式·数据分析
DngYT1 小时前
vue如何挂载路由
前端·javascript·vue.js
呵呵哒( ̄▽ ̄)"2 小时前
vue.js 展示树状结构数据,动态生成 HTML 内容
开发语言·前端·javascript·vue.js
安冬的码畜日常2 小时前
【CSS in Depth 2 精译_035】5.5 Grid 网格布局中的子网格布局(全新内容)
前端·css·css3·网格布局·css布局·子网格·subgrid
JuneTT2 小时前
uniapp 常用高度状态栏,导航栏,tab栏,底部安全高度
前端·javascript·uni-app
i80132 小时前
delphi制作漂亮的农历窗体(IntraWeb+Layui的完美结合)
前端·javascript·layui