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 中引入

相关推荐
崔庆才丨静觅40 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax