vue3+vite环境项目搭建

1.环境准备

(1) 安装Node 下载

(2) 安装VSCode 下载

(3)安装插件(TS环境必须)

插件市场搜索 Vue Language Features (Volar) TypeScript Vue Plugin (Volar) 安装,且禁用 Vetur

2.创建项目

npm create vite@latest

目录结构

3.路径别名配置

4.安装组件库(以elementUI plus为例)

根据官方文档进行安装 安装 | Element Plus (element-plus.org)

(1) 安装组件库:

(2) 按需引入

arduino 复制代码
npm install -D unplugin-vue-components unplugin-auto-import

(3)vite配置( vite.config.ts)

javascript 复制代码
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

(4)icon图标引入

typescript 复制代码
1.  安装element-plus Icon库-->  npm install @element-plus/icons-vue
2.  注册所有图标(main.ts)
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

5.安装CSS预处理语言(sass)

(1) 安装sass

js 复制代码
 npm i -D sass

(2)创建 variables.scss 变量文件,添加变量 $bg-color 定义,注意规范变量以 $ 开头

js 复制代码
// src/styles/variables.scss
$bg-color-red:#de3131;
$bg-color-blue:#206dc5;

(3) Vite 配置导入 SCSS 全局变量文件

js 复制代码
// vite.config.ts
css: {
    // CSS 预处理器
    preprocessorOptions: {
        //define global scss variable
        scss: {
            javascriptEnabled: true,
            additionalData: `@use "@/styles/variables.scss" as *;`
        }
    }
}

(4)style 标签使用SCSS全局变量

js 复制代码
<style lang="scss" scoped>
.testBox1{
    width: 100px;
    height: 100px;
    background-color:$bg-color-red;
}
.testBox2{
    width: 100px;
    height: 100px;
    background-color:$bg-color-blue;
}
</style>

6.安装原子化库(UnoCSS)

(1)安装

js 复制代码
npm install -D unocss

(2)vite.config.ts 配置

js 复制代码
import UnoCSS from 'unocss/vite'

export default {
  plugins: [
    UnoCSS({ /* options */ }),
  ],
}

(3)main.ts 引入 uno.css

js 复制代码
// src/main.ts
import 'uno.css'

(4)vscode 安装unocss插件

(5)vscode安装 UnoCSS 插件之后,输入类名没有智能提示。 参考地址UnoCSS 插件智能提示不生效解决_unocss插件不提示-CSDN博客

解决:

1)新建单独配置文件 uno.config.ts,配置好后重启vscode,如还未生效则进行第二步

js 复制代码
// uno.config.ts
import {
  defineConfig,
  presetAttributify,
  presetIcons,
  presetTypography,
  presetUno,
  presetWebFonts,
  transformerDirectives,
  transformerVariantGroup
} from 'unocss'
 
export default defineConfig({
  shortcuts: [
    // ...
  ],
  theme: {
    colors: {
      // ...
    }
  },
  presets: [
    presetUno(),
    presetAttributify(),
    presetIcons(),
    presetTypography(),
    presetWebFonts({
      fonts: {
        // ...
      },
    }),
  ],
  transformers: [
    transformerDirectives(),
    transformerVariantGroup(),
  ],
})

2)VScode 开启智能提示

检查配置文件是否有如下代码,没有请自行添加 配置好后的效果如下

7.安装pinia状态机(实现数据共享:跨组件,跨页面)

(1)安装

js 复制代码
npm install pinia

(2)新建store文件夹

index.ts

js 复制代码
import { createPinia } from "pinia";
const store = createPinia();
//子模块在主模块抛出
export { usePermissionStore } from "./modules/permission";
export { useUserStore } from "./modules/user";
export { useStationStore } from "./modules/station";
export * from "./modules/setting";

export default store;

子模块: user.ts

(4)在main.ts引入

(5)使用

父组件

子组件

效果:

初始状态

点击父组件

点击子组件

(6)数据持久化存储 配置 | pinia-plugin-persistedstate (prazdevs.github.io)

1)安装 pinia-plugin-persistedstate插件

js 复制代码
npm i pinia-plugin-persistedstate

2)在store/index.ts 注册该插件 3)使用

js 复制代码
**介绍三个常用属性:**

 1.key:存储名称。
 2.storage:存储方式。
 3.paths:用于指定 state 中哪些数据需要被持久化。`[]` 表示不持久化任何状态,`undefined` 或 `null` 表示持久化整个 state。

8.环境变量配置

1.参照vite文档环境变量和模式 | Vite 官方中文文档 (vitejs.dev)

(1).env.production 生产环境

(2).env.development 开发环境

(3) env.d.ts 环境变量类型定义,用于智能提示

默认情况下,开发服务器 (dev 命令) 运行在 development (开发) 模式,而 build 命令则运行在 production (生产) 模式。 (4)使用:

2.在Public文件夹下创建环境变量js文件 (1)新建环境变量文件

(2) 通过vite.config.ts配置文件引入

(3)在html入口文件使用

9.axios封装

(1)安装

js 复制代码
npm install axios

(2)封装axios,在utils/request.ts

js 复制代码
import  useUserStore from "@/store/modules/user";
import axios, { AxiosError, AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from "axios";
import { ElMessage } from "element-plus";

//定义类型
type ReqInterceptor = (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig;

interface VAxiosConfig {
  config: InternalAxiosRequestConfig;
  requestInterceptor?: ReqInterceptor;
}

//封装Axios类
export default class VAxios {
  private axiosInstance: AxiosInstance;
  private static VAxiosInstance: VAxios;

  private constructor(vConfig: VAxiosConfig) {
    const { config, requestInterceptor } = vConfig;
    this.axiosInstance = axios.create(config);
    this.setRequestInterceptors(requestInterceptor);
    this.setResponseInterceptors();
  }

  /** 创建实例 */
  public static createInstance(config: VAxiosConfig) {
    this.VAxiosInstance = new this(config);

    return this.VAxiosInstance;
  }

  /** 设置请求拦截器 */
  private setRequestInterceptors(reqInterceptor?: ReqInterceptor) {
    const onError = (error: AxiosError) => Promise.reject(error);
    if (reqInterceptor) {
      this.axiosInstance.interceptors.request.use(reqInterceptor, onError);
    } else {
      this.axiosInstance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
        const userStore = useUserStore();
        if (config.headers) {
          // 已登录才带上 Authorization
          if (userStore.userInfo?.access_token) {
            config.headers["Authorization"] = "Bearer " + userStore.userInfo.access_token;
          }
        }

        return config;
      }, onError);
    }
  }

  //? 跨域时 NetWork Error 没有拦截到
  /** 设置响应拦截器 */
  private setResponseInterceptors() {
    this.axiosInstance.interceptors.response.use(
      (response: AxiosResponse) => {
        if (typeof response.data === "object" && !response.data.code) {
          return response.data; // 文件导出
        }
        // // 设备找不到返回 500 // 不能直接返回,否则导致错误处理失效
        // if (response.data.code === 500) return response.data;
        if (response.data.code !== 200) {
          // console.log(response);
          // throw 终止后续逻辑
          throw ElMessage.error(response.data.message || "访问出错");
        }
        return response.data;
      },
      (error: AxiosError | Error) => {
        if (!axios.isAxiosError(error) || !error.response) return Promise.reject(error.message);

        switch (error.response.status) {
          case 0:
            throw ElMessage.error("网络错误");
          case 400:
            throw ElMessage.error("请求错误");
          case 401:
            throw ElMessage.error("请登录后操作");
          case 403:
            // router.replace("/403")
            throw ElMessage.error("403 权限不足");
          case 404:
            throw ElMessage.error("404 API 地址错误");
          case 405:
            throw ElMessage.error("405 方法不支持");
          case 500:
            throw ElMessage.error("500 服务器内部错误");
          case 502:
            throw ElMessage.error("502 网络服务器错误");
          case 504:
            throw ElMessage.error("504 网关超时");
          default:
            throw ElMessage.error(
              typeof error.response.data === "string" || error.response.data instanceof String
                ? String(error.response.data)
                : ""
              // : (error.response.data as unknown)?.message ?? (error.response.data as any)?.error
            );
        }
      }
    );
  }

  //删除请求头
  removeHeader(key: string) {
    if (!this.axiosInstance) return;
    delete this.axiosInstance.defaults.headers[key];
  }

  //设置请求头
  setHeaders(headers: Record<string, string>) {
    if (!this.axiosInstance) return;
    Object.assign(this.axiosInstance.defaults.headers, headers);
  }

  //请求方法

  /** 请求 */
  async request<T = unknown>(config: InternalAxiosRequestConfig): Promise<T> {
    return this.axiosInstance.request(config);
  }

  /** get 请求 */
  async get<T = unknown>(url: string, config?: InternalAxiosRequestConfig): Promise<T> {
    return this.axiosInstance.get(url, config);
  }

  /**
   * post 请求
   * @param url 请求地址
   * @param data body参数
   * @param config axios 配置
   */
  async post<T = unknown>(url: string, data?: unknown, config?: InternalAxiosRequestConfig): Promise<T> {
    return this.axiosInstance.post(url, data, config);
  }

  /**
   * put 请求
   * @param url 请求地址
   * @param data body参数
   * @param config axios 配置
   */
  async put<T = unknown>(url: string, data?: unknown, config?: InternalAxiosRequestConfig): Promise<T> {
    return this.axiosInstance.put(url, data, config);
  }

  /**
   * patch 请求
   * @param url 请求地址
   * @param data body参数
   * @param config axios 配置
   */
  async patch<T = unknown>(url: string, data?: unknown, config?: InternalAxiosRequestConfig): Promise<T> {
    return this.axiosInstance.patch(url, data, config);
  }

  /**
   * delete 请求
   * @param url 请求地址
   * @param config axios 配置
   */
  async delete<T = unknown>(url: string, config?: InternalAxiosRequestConfig): Promise<T> {
    return this.axiosInstance.delete(url, config);
  }
}

(2)http.ts

js 复制代码
import { API_URL } from "@/config";
import  useUserStore  from "@/store/models/user";
import VAxios from "./request";

export default VAxios.createInstance({
  config: {
    baseURL: API_URL,
    headers: {
      "Content-Type": "application/json; charset=utf-8",
      Accept: "*/*",
    },
    // 考虑到文件上传
    timeout: 1 * 90 * 1000,
  },
  requestInterceptor: (config) => {
    const userStore = useUserStore();
    if (config.headers) {
      // 已登录才带上 Authorization
      if (userStore.userInfo?.access_token) {
        config.headers["Authorization"] = "Bearer " + userStore.userInfo.access_token;
      }
    }

    return config;
  },
});

(3)写API接口

js 复制代码
import request from "@/utils/http";
import { UserLoginRes } from "@/types/user";
import { GetUserAuthRes } from "./models/user";

/** 用户登录 API */
export async function loginApi(username: string, password: string) {
  return request.post<UserLoginRes>("/oauth/login", undefined, {
    params: {
      username,
      password,
      grant_type: "password",
      client_secret: "iUD4dCYxn6AetqRJ2v",
      client_id: "tenant_pc",
    },
    headers: {
      platform: "pc",
    },
  });
}

/**
 * @link /api/v1/user/auth/authorities
 * 获取权限列表
 */
export function getUserAuthApi() {
  return request.get<GetUserAuthRes>("/api/v1/user/auth/authorities");
}

10.vue-router

目录结构

(1)安装

js 复制代码
npm i vue-router

(2)配置路由 router/index.ts

js 复制代码
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
import errorRoutes from "./models/error";
import others from "./models/other";

const baseRoutes: Array<RouteRecordRaw> = [
  {
    path: "/",
    redirect: "/home",
  },
  {
    path: "/login",
    component: () => import("@/views/login.vue"),
  },
  ...others,
];

const routes = baseRoutes.concat(errorRoutes);

const router = createRouter({
  history: createWebHashHistory(import.meta.env.BASE_URL),
  routes,
});

export default router;

(3)路由守卫

js 复制代码
/** 路由守卫 */
import  useUserStore  from "@/store/models/user";
import { ElLoading, ElMessage } from "element-plus";
import router from ".";
// import NProgress from "nprogress";
import "nprogress/nprogress.css";

const loginPath = "/login";
let elLoading: null | { close: () => void } = null;

router.beforeEach((to, from) => {
  // NProgress.start();
  elLoading = ElLoading.service({
    lock: true,
    text: "加载中",
    background: "rgba(0, 0, 0, 0.7)",
  });

  /** token 不存在或过期则返回 true */
  function isNoTokenOrExpired() {
    const { userInfo } = useUserStore();
    if (!userInfo || !userInfo.access_token) return true;
    if (!userInfo || !userInfo.expires_at) return true;
    if (userInfo.expires_at < Date.now()) return true;
    return false;
  }
  // 未登录或 token 过期则跳转登录页面
  if (to.path !== loginPath && isNoTokenOrExpired()) {
    ElMessage.error("请重新登录");
    return "/login";
  }
  // 登录且 token 未过期则不允许访问登录页面
  if (to.path === "/login" && !isNoTokenOrExpired()) {
    return from.path || "/home";
  }
});

router.afterEach(() => {
  // NProgress.done();
  elLoading?.close();
});

(4)在main.ts 中引入

相关推荐
狂奔solar10 分钟前
分享个好玩的,在k8s上部署web版macos
前端·macos·kubernetes
qiyi.sky12 分钟前
JavaWeb——Web入门(8/9)- Tomcat:基本使用(下载与安装、目录结构介绍、启动与关闭、可能出现的问题及解决方案、总结)
java·前端·笔记·学习·tomcat
清云随笔34 分钟前
axios 实现 无感刷新方案
前端
鑫宝Code35 分钟前
【React】状态管理之Redux
前端·react.js·前端框架
忠实米线43 分钟前
使用pdf-lib.js实现pdf添加自定义水印功能
前端·javascript·pdf
pink大呲花1 小时前
关于番外篇-CSS3新增特性
前端·css·css3
少年维持着烦恼.1 小时前
第八章习题
前端·css·html
我是哈哈hh1 小时前
HTML5和CSS3的进阶_HTML5和CSS3的新增特性
开发语言·前端·css·html·css3·html5·web
田本初1 小时前
如何修改npm包
前端·npm·node.js