vite+react搭建人力管理系统项目(1)

一 、vite命令构建(yarn搭建)
yarn create vite----根据提示填写

yarn create vite 项目名称 --template react-ts

二 、配置vite启动命令以及添加文件目录
1.vite.config.ts

export default defineConfig({
  plugins: [react()],
   	// 服务器选项
   server:{
   host:'0.0.0.0',// 输入ip可以访问,指定服务器应该监听哪个 IP 地址。 如果将此设置为 0.0.0.0 或者 true 将监听所有地址,包括局域网和公网地址。
   open:true,//开发服务器启动时,自动在浏览器中打开应用程序。
 }
});

运行结果ip展示为:
➜ Local: http://localhost:5173/
➜ Network: http://2.0.0.1:5173/
➜ Network: http://192.168.xxx.xxx:5173/
2.文件目录
react项目基本src目录需要添加的有:路由router,状态管理库redux,接口请求api,公共组件Components,全局常量constants(按照项目而定),钩子hooks,布局layout,页面pages,工具utils,插件声明plugins.d.ts(按照项目而定)

vite+react搭建人力管理系统项目(1)_第1张图片
同时在vite.config.ts和tsconfig.json配置@/xxx 目录

 // 路径变量vite.config.ts
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
    },
  },
 "compilerOptions":{
 .......
  "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
 }

3.安装antd router6 redux (提示:先把项目停止再安装)
yarn add antd
yarn add react-router-dom@6
yarn add react-redux @reduxjs/toolkit
安装less
yarn add less
vite.config.ts配置less

 // css 预处理器
    css: {
      preprocessorOptions: {
        less: {
          javascriptEnabled: true,
        },
      },
    },

三、基本路由编写
1.新建home和login页面
pages/index.ts 统一暴露

import { lazy } from "react";

export const PageHome = lazy(() => import("@/pages/home"));
export const PageLogin = lazy(() => import("@/pages/login"));

2.实现路由跳转
router/index.tsx

const routeList: RouteObject[] = [
  {
    path: "/home",
    element: <PageHome />,
  },
  {
    path: "/login",
    element: <PageLogin />,
  },
];
const RouterRender = () => {
  return useRoutes(routeList);
};
export default RouterRender;

App.tsx

function App() {
  return (
    <BrowserRouter>
      <RouterRender />
    </BrowserRouter>
  );
}

3.基本路由编写

// 导航路由
const navRoutes = [
  {
    path: "/home",
    element: lazyLoad(<PageHome />),
    meta: {
      title: "首页",
      icon: "HomeOutlined",
    },
  },
];
// 全屏路由
const staticRoutes = [
  {
    path: "./login",
    element: lazyLoad(<PageLogin />),
    meta: {
      title: "登录",
    },
  },
];
// 容错路由
const notFoundAndNoPower = [
  {
    path: "*",
    element: <Page404 />,
    meta: {
      title: "notFound",
    },
  },
  {
    path: "*",
    element: <Page403 />,
    meta: {
      title: "notPower",
    },
  },
];

const routeList: RouteObject[] = [
  ...staticRoutes,
  ...navRoutes,
  ...notFoundAndNoPower,
];
const RouterRender = () => {
  return useRoutes(routeList);
};
// export const localRouters=getNavRouter(navRoutes)
export default RouterRender;

四、Layout布局
1.使用ant全局配置
App.tsx

import RouterRender from "@/router";
import { BrowserRouter } from "react-router-dom";
import { ConfigProvider } from "antd";
import zhCN from "antd/es/locale/zh_CN";
import "antd/dist/reset.css";
function App() {
  return (
    <ConfigProvider locale={zhCN}>
      <BrowserRouter>
        <RouterRender />
      </BrowserRouter>
    </ConfigProvider>
  );
}

export default App;

2.编写layou基本页面以及路由
layout->index.tsx
(里面具体组件样式自己编写)

import { Layout } from "antd";
import styles from "./layout.module.less";
import React from "react";
import ComponentHeader from "./components/ComponentHeader";
import ComponentTabsView from "./components/TabsView";
import { Outlet } from "react-router";
import { LAYOUTS } from "@/constans";
const { Header, Footer, Sider, Content } = Layout;


const ComponentLayout: React.FC = () => {
  return (
    <Layout>
      <ComponentHeader />
      <Layout className={styles.container}>
        <Sider className={styles.sider} width={206}></Sider>
        <Content className={styles.content}>
          <ComponentTabsView />
          <Content className={styles.outlet}>
            <Outlet />
          </Content>
          <Footer className={styles.footer}>{LAYOUTS.copyright}</Footer>
        </Content>
      </Layout>
    </Layout>
  );
};
export default React.memo(ComponentLayout);

layout->layout.module.less

.container {
  height: calc(100vh - 48px);
  background-color: red;
  .sider {
    background-color: #fff;
  }
  .outlet {
    margin: 16px;
    height: calc(100vh - 48px - 30px - 46px - 51px);
    background-color: rebeccapurple;
  }
  .footer {
    text-align: center;
    font-size: 12px;
    box-shadow: 0 0 5px 1px hsl(0deg 0% 67% / 20%);
  }
}

路由修改

const routeList: RouteObject[] = [
  {
    path: "/",
    element: <ComponentLayout />,
    children: navRoutes,
  },
   ...staticRoutes,
   ...notFoundAndNoPower,
];

展示效果
vite+react搭建人力管理系统项目(1)_第2张图片
利用路由懒加载Suspense结合nprogress
yarn add nprogress(别忘了plugins.d.ts声明)
component–>Nprogress–>index.tsx

import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { useEffect } from "react";
const ComponentNprogress: React.FC = () => {
  NProgress.start();
  useEffect(() => {
    NProgress.done();
  }, []);
  return <div></div>;
};
export default ComponentNprogress;

router.tsx
之前统一导出的页面借用lazy内置方法,依赖内置组件Suspense,给lazy加上loading指示器组件的一个容器组件(Suspense目前只和lazy配合实现组件等待加载指示器的功能)
编写路由的时候使每个路由对象被lazyLoad包裹这样在layout里面切换路由的时候就实现路由懒加载(上面也会有进度条)

// 懒加载组件
const lazyLoad = (children: ReactNode) => (
  <Suspense fallback={<ComponentNprogress />}>{children}</Suspense>
);

五、store-----@
reduxjs/toolkit
redux官网https://redux-toolkit.js.org/tutorials/typescript
基本目录,目前只能确定一个用户相关的切片先写文件
在这里插入图片描述
index.ts创建toolkit

import { configureStore } from "@reduxjs/toolkit";

const store = configureStore({
  reducer: {},
});
export default store;

六、配置环境变量、模式
1.vite官网:https://cn.vitejs.dev/guide/env-and-mode.html#env-files
具体情况根据项目而定
由于本项目后端不处理跨域,所以前端开发的时候需要代理,为了区分两种环境不同,代理地址开发环境为/gateway/;生产环境和测试环境为/api(/为相对路径,所以请求的时候看到的是:服务器ip地址/api/接口地址,看不到真是的后端地址的)
.evn.devlopment

#开发环境
VITE_ENV='delevopment'

VITE_API_URL='/getway'

VITE_SERVER_NAME='http://1x.1x5.1x3.222:18082'

.env.production

 #线上环境
VITE_ENV='production'

VITE_API_URL='/api'

VITE_SERVER_NAME='http://x0.1xx.1x3.x22:18082'

本地开发代理
vite.config.js

// 服务器选项
    server: {
      host: "0.0.0.0", // 输入ip可以访问,指定服务器应该监听哪个 IP 地址。 如果将此设置为 0.0.0.0 或者 true 将监听所有地址,包括局域网和公网地址。
      open: true, //开发服务器启动时,自动在浏览器中打开应用程序。
      proxy: {
        "/getway": {
          target: loadEnv(mode, process.cwd()).VITE_SERVER_NAME, //获取当前环境的变量
          rewrite: (path) => path.replace(/^\/getway/, ""),//发送请求的时候把代理的地址删了
        },
      },
    },

测试环境和线上环境都是用nginx代理
打包之后根据不同的环境配置nginx代理,代理地址就是/api了(具体配置自行百度哈)
2.模式
vite+react搭建人力管理系统项目(1)_第3张图片

默认的通过运行不同命令走不同的环境不用单独配置了

七、请求api
1.请求需要token,存在cookie中,所以先写一个工具对浏览器缓存的处理
utils–>storage.ts
yarn add js-cookie

import Cookies from "js-cookie";
/** 
*window.localStorage浏览器永久缓存
* @method set 设置永久缓存
  @method get 设置永久缓存
  @method remove 设置永久缓存
  @method clear 设置永久缓存
*/
export const Local = {
  // 设置永久缓存
  set(key: string, val: any) {
    // 防止token被误存再local中
    if (key === "token") return Cookies.set(key, val);
    window.localStorage.setItem(key, JSON.stringify(val));
  },
  //获取永久缓存
  get(key: string) {
    if (key === "token") return Cookies.get(key);
    let json: any = window.localStorage.getItem(key);
    return JSON.parse(json);
  },
  // 移除永久缓存
  remove(key: string) {
    if (key === "token") return Cookies.remove(key);
    window.localStorage.removeItem(key);
  },
  // 移除全部永久缓存
  clear() {
    Cookies.remove("token");
    window.localStorage.clear();
  },
};
/** 
*window.localStorage浏览器临时缓存
* @method set 设置临时缓存
  @method get 设置临时缓存
  @method remove 设置临时缓存
  @method clear 设置临时缓存
*/
export const Session = {
  // 设置临时缓存
  set(key: string, val: any) {
    window.sessionStorage.setItem(key, JSON.stringify(val));
  },
  // 获取临时缓存
  get(key: string) {
    let json: any = window.sessionStorage.getItem(key);
    return JSON.parse(json);
  },
  // 移除临时缓存
  remove(key: string) {
    window.sessionStorage.removeItem(key);
  },
  // 移除全部临时缓存
  clear() {
    window.sessionStorage.clear();
  },
};

同时要声明一下,要不ts不认识js-cookie
plugins.ts

declare module "js-cookie";

2.写api请求
yarn add axios
api–>serve.ts

/*
 * @Description:api请求
 * @Author: zt
 * @Date: 2023-03-13 15:48:57
 * @LastEditors: zt
 * @LastEditTime: 2023-03-17 11:40:10
 */
import axios from "axios";
import {
  handleAuth,
  handleRequestHeader,
  handleNetworkError,
  handleAuthError,
  handleGeneralError,
} from "./tools";

// 请求拦截
axios.interceptors.request.use((config) => {
  config = handleRequestHeader(config);
  config = handleAuth(config);
  return config;
});

// 响应拦截
axios.interceptors.response.use(
  (response) => {
    // 请求响应不成功直接返回错误
    if (response.status !== 200) return Promise.reject(response.data);
    // 请求通过进行拦截处理(登录失效、接口返回非200)
    handleAuthError(response.data.code) &&
      handleGeneralError(response.data.code, response.data.message);
    return response;
  },
  (err) => {
    // 请求未成功
    handleNetworkError(err?.response?.status);
    Promise.reject(err.response);
  }
);

interface FcResponse<T> {
  code: number;
  message: string;
  data: T;
}
interface IAnyObj {
  [index: string]: unknown;
}
// 请求方式
export const Get = <T>(
  url: string,
  params: IAnyObj = {},
  config?: any
): Promise<[any, FcResponse<T> | undefined]> =>
  new Promise((resolve) => {
    axios
      .get(url, { params, ...config })
      .then((result) => {
        resolve([null, result.data as FcResponse<T>]);
      })
      .catch((err) => {
        resolve([err, undefined]);
      });
  });

export const Post = <T>(
  url: string,
  data: IAnyObj = {},
  params: IAnyObj = {},
  config?: any
): Promise<[any, FcResponse<T> | undefined]> => {
  return new Promise((resolve) => {
    axios
      .post(url, data, { params, ...config })
      .then((result) => {
        resolve([null, result.data as FcResponse<T>]);
      })
      .catch((err) => {
        resolve([err, undefined]);
      });
  });
};

export const Put = <T>(
  url: string,
  data: IAnyObj = {},
  params: IAnyObj = {}
): Promise<[any, FcResponse<T> | undefined]> => {
  return new Promise((resolve) => {
    axios
      .put(url, data, { params })
      .then((result) => {
        resolve([null, result.data as FcResponse<T>]);
      })
      .catch((err) => {
        resolve([err, undefined]);
      });
  });
};

export const Delete = <T>(
  url: string,
  params: IAnyObj = {},
  data: IAnyObj = {},
  config?: any
): Promise<[any, FcResponse<T> | undefined]> => {
  return new Promise((resolve) => {
    axios
      .delete(url, { params, data, ...config })
      .then((result) => {
        resolve([null, result.data as FcResponse<T>]);
      })
      .catch((err) => {
        resolve([err, undefined]);
      });
  });
};

api–>tools.ts

import { Local, Session } from "@/utils/storage";
import { message } from "antd";
export const handleRequestHeader = (config: any) => {
  config["baseURL"] = import.meta.env.VITE_API_URL as any;
  config["timeout"] = "20000";
  return config;
};
export const handleAuth = (config: any) => {
  config.headers["token"] = Local.get("token") || "";
  return config;
};

export const handleAuthError = (errno: any) => {
  const authErrMap: any = {
    5000: "登陆失效,需求重新登录", //token失效
  };
  // Object.hasOwnProperty.call(object, key) 是用于检查一个对象(object)
  // 是否具有一个指定属性(key)的方法,它返回一个布尔值。
  if (Object.prototype.hasOwnProperty.call(authErrMap, errno)) {
    Session.clear();
    Local.clear();
    window.location.reload(); //刷新当前页面
    return false;
  }
  return true;
};

export const handleGeneralError = (errno: any, errmsg: any) => {
  if (errno && errno != 200) {
    message.error(errmsg ?? "未知错误");
    return false;
  }
  return true;
};

export const handleNetworkError = (errStatus: number) => {
  let errMessage = "未知错误";
  const errMsgMap: any = {
    400: "请求的错误",
    401: "未授权,请重新登录",
    403: "拒绝访问",
    404: "请求错误,未找到该资源",
    405: "请求方法未允许",
    408: "请求超时",
    500: "服务器端出错",
    501: "网络未实现",
    502: "网络错误",
    503: "服务不可用",
    504: "网络超时",
    505: "http版本不支持该请求",
  };
  if (errStatus) {
    errMessage = errMsgMap[errStatus] || `其他连接错误--${errStatus}`;
  } else {
    errMessage = `无法连接到服务器`;
  }
  message.error(errMessage);
};

基本配置已经写完的,下一步编写页面具体的逻辑…

你可能感兴趣的:(react.js,javascript,前端,typescript)