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

相关推荐
Summer不秃3 分钟前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰7 分钟前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
Viktor_Ye14 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm16 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
乐闻x42 分钟前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
一条晒干的咸魚44 分钟前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
花海少爷1 小时前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript
Amd7941 小时前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子
生椰拿铁You1 小时前
09 —— Webpack搭建开发环境
前端·webpack·node.js
狸克先生1 小时前
如何用AI写小说(二):Gradio 超简单的网页前端交互
前端·人工智能·chatgpt·交互