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

相关推荐
玩电脑的辣条哥3 小时前
Python如何播放本地音乐并在web页面播放
开发语言·前端·python
ew452183 小时前
ElementUI表格表头自定义添加checkbox,点击选中样式不生效
前端·javascript·elementui
suibian52353 小时前
AI时代:前端开发的职业发展路径拓宽
前端·人工智能
Moon.93 小时前
el-table的hasChildren不生效?子级没数据还显示箭头号?树形数据无法展开和收缩
前端·vue.js·html
垚垚 Securify 前沿站3 小时前
深入了解 AppScan 工具的使用:筑牢 Web 应用安全防线
运维·前端·网络·安全·web安全·系统安全
工业甲酰苯胺6 小时前
Vue3 基础概念与环境搭建
前端·javascript·vue.js
mosquito_lover17 小时前
怎么把pyqt界面做的像web一样漂亮
前端·python·pyqt
柴柴的小记9 小时前
前端vue引入特殊字体不生效
前端·javascript·vue.js
柠檬豆腐脑10 小时前
从前端到全栈:新闻管理系统及多个应用端展示
前端·全栈
bin915310 小时前
DeepSeek 助力 Vue 开发:打造丝滑的颜色选择器(Color Picker)
前端·javascript·vue.js·ecmascript·deepseek