Vue3+ts后台管理项目详记(待续)

619 阅读15分钟

环境搭建

创建项目

可以使用 Vue CLI 或 Vite来创建初始化一个vue3项目,我是使用vite(目前官方也推荐使用vite),使用的是yarn管理器

# For Yarn Modern (v2+)
yarn create vue@latest

# For Yarn ^v4.11
yarn dlx create-vue@latest
✔ Project name: … <vue3_project> 
✔ Add TypeScript? … No / Yes 
✔ Add JSX Support? … No / Yes 
✔ Add Vue Router for Single Page Application development? … No / Yes 
✔ Add Pinia for state management? … No / Yes ✔ Add Vitest for Unit testing? … No / Yes 
✔ Add an End-to-End Testing Solution? … No / Cypress / Nightwatch / Playwright 
✔ Add ESLint for code quality? … No / Yes ✔ Add Prettier for code formatting? … No / Yes 
✔ Add Vue DevTools 7 extension for debugging? (experimental) … No / Yes Scaffolding project in ./<your-project-name>... Done.

ESLint+Prettier,代码规范,Prettier可以让代码格式化,好看

代码规范

1、editorconfig统一编码风格

EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。

根目录下创建 .editorconfig 文件

# http://editorconfig.orgroot = true[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false

CRLF、CR、LF详解

很长一段时间里,对于CRLF、CR、LF的理解仅限于不同操作系统下对换行符的定义。

  • CR:Carriage Return,对应ASCII中转义字符\r,表示回车

  • LF:Linefeed,对应ASCII中转义字符\n,表示换行

  • CRLF:Carriage Return & Linefeed,\r\n,表示回车并换行

    众所周知,Windows操作系统采用两个字符来进行换行,即CRLF;

    Unix/Linux/Mac OS X操作系统采用单个字符LF来进行换行;

    另外,MacIntosh操作系统(即早期的Mac操作系统)采用单个字符CR来进行换行。

VSCode需要安装一个插件才可以读取这个文件:EditorConfig for VS Code,webstorm不需要安装

image.png

2、prettier工具_格式化代码

Prettier 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。

1.安装prettier

npm install prettier -D

2.配置.prettierrc文件:

{
  "useTabs": false,
  "tabWidth": 2,
  "printWidth": 80,
  "singleQuote": false,
  "trailingComma": "none",
  "semi": false,
  "endOfLine": "auto",
   "htmlWhitespaceSensitivity": "ignore"
}

代码拉取到本地可能会有类似error Deleteprettier/prettier的错误导致项目运行失败,是因为prettier对行尾控制换行类型检验的问题,可以设置"endOfLine": "auto",取消对该项的检验

/*  prettier的配置 */
"prettier.printWidth": 100, // 超过最大值换行
"prettier.tabWidth": 4, // 缩进字节数
"prettier.useTabs": false, // 缩进不使用tab,使用空格
"prettier.semi": true, // 句尾添加分号
"prettier.singleQuote": true, // 使用单引号代替双引号
"prettier.proseWrap": "preserve", // 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行
"prettier.arrowParens": "avoid", //  (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号
"prettier.bracketSpacing": true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }"
"prettier.disableLanguages": ["vue"], // 不格式化vue文件,vue文件的格式化单独设置
"prettier.endOfLine": "auto", // 结尾是 \n \r \n\r auto
"prettier.eslintIntegration": false, //不让prettier使用eslint的代码格式进行校验
"prettier.htmlWhitespaceSensitivity": "ignore",//⭐结束标签的结尾尖括号掉到了下一行
"prettier.ignorePath": ".prettierignore", // 不使用prettier格式化的文件填写在项目的.prettierignore文件中
"prettier.jsxBracketSameLine": false, // 在jsx中把'>' 是否单独放一行
"prettier.jsxSingleQuote": false, // 在jsx中使用单引号代替双引号
"prettier.parser": "babylon", // 格式化的解析器,默认是babylon
"prettier.requireConfig": false, // Require a 'prettierconfig' to format prettier
"prettier.stylelintIntegration": false, //不让prettier使用stylelint的代码格式进行校验
"prettier.trailingComma": "es5", // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号)
"prettier.tslintIntegration": false // 不让prettier使用tslint的代码格式进行校验

有的文件不需要进行代码格式化,可以创建下面的文件

3.创建.prettierignore忽略文件

/dist/*
.local
.output.js
/node_modules/**
​
**/*.svg
**/*.sh
​
/public/*

4.VSCode需要安装prettier的插件

image.png

进入插件配置设置与.prettierrc文件相同的格式化风格⭐

5.测试prettier是否生效

  • 测试一:在代码中保存代码;
  • 测试二:配置一次性修改的命令;

如果保存时不能格式化,可能是因为vscode的设置没有勾选

image.png 代码中保存一次只能格式化一个文件,如果想一次性格式化除忽略外的所有文件,可以在pakeage.json中配置脚本

在package.json中配置一个scripts:

"prettier": "prettier --write ."

补充:

// 使能每一种语言默认格式化规则 `settings.json`
"[html]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},

prettier插件失效可能原因:

image.png

Require config set to true and no config present. Skipping file.

解决:扩展设置 > 去掉勾选

image.png

3、使用ESLint检测

1.在前面创建项目的时候,我们就选择了ESLint,所以Vue会默认帮助我们配置需要的ESLint环境。

2.VSCode需要安装ESLint插件:

image.png 3.解决eslint和prettier冲突的问题:

安装插件:(vue在创建项目时,如果选择prettier,那么这两个插件会自动安装)

npm i eslint-plugin-prettier eslint-config-prettier -D

.eslintrc.js中添加prettier插件:

  extends: [
    "plugin:vue/essential",  //vue2
    "plugin:vue/vue3-essential", //vue3
    "eslint:recommended",
    "@vue/typescript/recommended",  //ts校验
    "@vue/prettier",
    "@vue/prettier/@typescript-eslint", //ts校验
    'plugin:prettier/recommended'
  ],
npm i @vue/eslint-config-prettier -D

image.png

4、husky和eslint

虽然我们已经要求项目使用eslint了,但是不能保证组员提交代码之前都将eslint中的问题解决掉了

  • 也就是我们希望保证代码仓库中的代码都是符合eslint规范的;
  • 那么我们需要在组员执行 git commit 命令的时候对其进行校验,如果不符合eslint规范,那么自动通过规范进行修复

那么如何做到这一点呢?可以通过Husky工具:

  • husky是一个git hook工具,可以帮助我们触发git提交的各个阶段:pre-commit、commit-msg、pre-push

如何使用husky呢?

这里我们可以使用自动配置命令:注意⭐:这里自动执行这个命令就会做下面的三件事,如果你只是安装husky-init,那么请自己做下面的三件事

npx husky-init && npm install
npx husky-init '&&' npm install
//或者
npx husky-init; npm install

使用上面的命令就会做下面三件事:(如果失败就手动做下面这几件事)

throw new Error(.git can't be found (see ${url}));

如果报这种错可能是因为该项目文件夹中没有.git文件夹,就是没有连接远端

1.安装husky相关的依赖:

npm i husky -D

image.png

2.在项目目录下创建 .husky 文件夹:

npx husky install

image.png 3.在package.json中添加一个脚本:

image.png

接下来,我们需要去完成一个操作:在进行commit时,执行lint脚本,就是在组员执行commit提交时,它会自动执行这个命令进行检测,如果不规范会纠正代码格式 image.png

这个时候我们执行git commit的时候会自动对代码进行lint校验。

注意:vscode里面的终端打开的其实是windows系统里面的powershell(微软弄得,想挤掉cmd)

好用排行:git bash>cmd>powershell

像上面的 npx husky-init && npm install 命令中的 && 连接符在cmd中识别不了,但是在git bash里面就可以,如果想在cmd中使用,可以把&&加上引号'&&'

5、git commit规范

5.1. 代码提交风格

通常我们的git commit会按照统一的风格来提交,这样可以快速定位每次提交的内容,方便之后对版本进行控制。

不统一的话,你的提交就是自己写的信息,有的信息是不规范的,看看尤雨溪提交的规范信息

image.png

但是如果每次手动来编写这些是比较麻烦的事情,我们可以使用一个工具:Commitizen

  • Commitizen 是一个帮助我们编写规范 commit message 的工具;

1.安装Commitizen

npm install commitizen -D

2.安装cz-conventional-changelog,并且初始化cz-conventional-changelog:

npx commitizen init cz-conventional-changelog --save-dev --save-exact

这个命令会帮助我们安装cz-conventional-changelog:

image.png

并且在package.json中进行配置:

image.png

这个时候我们提交代码需要使用 npx cz而不是使用 git commit

  • 第一步是选择type,本次更新的类型
Type作用
feat新增特性 (feature)
fix修复 Bug(bug fix)
docs修改文档 (documentation)
style代码格式修改(white-space, formatting, missing semi colons, etc)
refactor代码重构(refactor)
perf改善性能(A code change that improves performance)
test测试(when adding missing tests)
build变更项目构建或外部依赖(例如 scopes: webpack、gulp、npm 等)
ci更改持续集成软件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等
chore变更构建流程或辅助工具(比如更改测试环境)
revert代码回退
  • 第二步选择本次修改的范围(作用域)

image.png

  • 第三步选择提交的信息

image.png

  • 第四步提交详细的描述信息

image.png

  • 第五步是否是一次重大的更改(直接回车默认就是no)

image.png

  • 第六步是否影响某个open issue(开源项目会用到)

image.png

我们也可以在scripts中构建一个命令来执行 cz: image.png

本地安装的commitizen如果是用不了,可以全局安装,之后使用cz命令代替npx cz,使用的就是全局安装的commitizen

5.2. 代码提交验证commitlint

如果我们按照cz来规范了提交风格,但是依然有同事通过 git commit 按照不规范的格式提交应该怎么办呢?

  • 我们可以通过commitlint来限制提交;

1.安装 @commitlint/config-conventional 和 @commitlint/cli

npm i @commitlint/config-conventional @commitlint/cli -D

2.在根目录创建commitlint.config.js文件,配置commitlint

module.exports = {
  extends: ['@commitlint/config-conventional']
}

3.使用husky生成commit-msg文件,验证提交信息(拦截组员编写的提交信息)

npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

image.png

之后修改,使用git commit -m '哈哈哈' 就会提交不成功,还是需要使用 npx cz

node-modules文件夹下的 .bin文件夹中,都是配置的脚本,执行这里的脚本要使用:npx 脚本名

_src文件夹别名配置

Vite 项目,在 vite.config.js 文件中配置别名:

javascript
// vite.config.js
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      // 其他别名配置
    }
  }
});

在这个配置中,@ 被设置为 src 目录的别名。

如果你的项目使用 TypeScript,你还需要在 tsconfig.json 文件中配置路径映射:

json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

在这个配置中,"@/*" 表示任何以 @ 开头的导入路径都会被映射到 src 目录下的相应路径。

注意事项

  • 如果代码报错
//根据项目中使用的构建工具进行安装
npm install --save-dev @types/node
yarn add --dev @types/node
  • 确保别名不与现有的模块或包冲突。
  • 在使用别名时,不需要加 ~ 或其他特殊字符,直接使用 @ 或你定义的别名即可。
  • 如果你的项目中有多个 tsconfig.json 文件(例如使用了 TypeScript 的项目引用),确保在每个相关的 tsconfig.json 文件中都添加了别名配置。

配置完成后,你就可以在项目中使用别名了,例如:

// 使用别名导入组件
import HelloWorld from "@/components/HelloWorld.vue";

第三方库集成

1、vue.config.js集成

vue.config.js有三种配置方式: 查看vue cli文档

  • 方式一:直接通过CLI提供给我们的选项来配置:

    • 比如publicPath:配置应用程序部署的子目录(默认是 /,相当于部署在 https://www.my-app.com/);
    • 比如outputDir:修改输出的文件夹;
  • 方式二:通过configureWebpack修改webpack的配置:

    • 可以是一个对象,直接会被合并;
    • 可以是一个函数,会接收一个config,可以通过config来修改配置;
  • 方式三:通过chainWebpack修改webpack的配置:

    • 是一个函数,会接收一个基于 webpack-chain 的config对象,可以对配置进行修改;
const path = require('path')
​
module.exports = {
  //配置方式一:使用vue cli提供的配置,与webpack类似,查看vue cli文档
  outputDir: './build',
​
  //配置方式二:和webpack属性完全一致
  //1、配置在configureWebpack对象内:最后会和原有配置进行合并
  // configureWebpack: {
  //     resolve: {
  //         alias: {
  //             components: '@/components'
  //         }
  //     }
  // },
​
  //2、配置在configureWebpack函数内:重新配置的属性会把原有的属性进行覆盖
  // configureWebpack: (config) => {
  //     //重新配置原有配置
  //     config.resolve.alias = {
  //         '@': path.resolve(__dirname, 'src'),
  //         components: '@/components'
  //     }
  // },
​
  // 3.配置方式三:webpack链式编程,覆盖
  chainWebpack: (config) => {
    config.resolve.alias
      .set('@', path.resolve(__dirname, 'src'))
      .set('components', '@/components')
  }
}

这里可能会有一个报错,这是ESLint的报错,他不让我们使用commonjs的模块导入,但是如果我们像这样做的话,可以在 .eslintrc.js里面把这个检测配置为off image.png

2、vue-router-ts集成

导入类型,import后面可以加上 type 以示区分,也可以不加

import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import localCache from '@/utils/cache'
import Login from '../views/login/index.vue'const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    redirect: '/main'
  },
  {
    path: '/login',
    name: 'Login',
    component: Login
  },
  {
    path: '/main',
    name: 'Main',
    component: () =>
      import(/* webpackChunkName: "about" */ '../views/main/index.vue')
  }
]
​
const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})
​
//判断token,如果没有,一律跳转到登陆页面
router.beforeEach((to) => {
  if (to.path !== '/login') {
    const token = localCache.getCache('token')
    if (!token) {
      return '/login'
    }
  }
})
​
export default router
​

import type { RouteRecordRaw } from 'vue-router'

import 后面有一个type,只是表示导入的模块是类型,不加也可以

3、vuex-ts集成

vuex对于ts的语法来说是不完善的,在ts中使用vuex是非常不好使用的。。。

index.ts入口文件:

rootState是state的类型,建议传入

import { createStore } from 'vuex'
import login from './login'
import type { rootState } from './types'interface rootState {
  name: string
  age: number
}
​
export default createStore<rootState>({
  state() {
    return {
      name: '',
      age: 0
    }
  },
  mutations: {},
  actions: {},
  modules: {
    login//login模块
  }
})

login模块:

模块的类型需要单独导入,并且传入两个泛型

import type { Module } from 'vuex'
import type { rootState } from '../types'export interface loginState {
  token: string
  userInfo: any
  menus: Array<any>
}
​
const loginModule: Module<loginState, rootState> = {
  //Module<S,R>,这里的Module必须传递两个泛型类型(点击源码查看),S是当前模块的state类型,R是rootstate的类型⭐⭐
  namespaced: true,
  state() {
    return {
      token: '',
      userInfo: {},
      menus: []
    }
  },
  getters: {},
  mutations: {
    setToken(state, payload) {
      state.token = payload
    },
    setUserInfo(state, payload) {
      state.userInfo = payload
    },
    setMenus(state, payload) {
      state.menus = payload
    }
  },
  actions: {}
}
export default loginModule
​

4、elementplus集成

按需引入,详见项目配置及文档

main.ts

createApp(App)的类型就是App //import type { App } from 'vue'

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'//按需引入
import 'element-plus/packages/theme-chalk/lib/base.css'
import registerApp from './global'const app = createApp(App)
registerApp(app)
app.use(store).use(router).mount('#app')

5、axios-ts集成

//res的类型问题:
import axios from 'axios'
//promise是有类型的,泛型类,resolve参数的类型就是res的类型,定义的泛型的类型就是这两个的类型,在axios里面定义好了res的类型
const p = new Promise<string>((resolve) => {
  resolve('uyfb')
})
p.then((res) => {
  console.log(res.length)
})
axios.all([
  axios.get('127.0.0.1:3000/user'),
  axios.get('127.0.0.1:3000/pic')
]).then(res=>console.log(res))//res是一个数组
const token = 'this is token'
// 请求拦截器:两个参数
/**
 * 参数一:请求发送成功会执行的函数
 * 参数二:请求发送失败会执行的函数
 */
axios.interceptors.request.use(
  (config) => {
    config.headers['authorization'] = 'Bearer ' + token
    return config
  },
  (err) => {
    console.log('这次请求发送失败了哦')
  }
)
//响应拦截器
/**
 * 参数一:响应成功会执行的函数
 * 参数二:响应失败会执行的函数
 */
axios.interceptors.response.use(res=>{
  return res.data
},err=>{
  console.log('响应失败了')
})
axios封装

封装类service/request/index.ts

import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { ElLoading } from 'element-plus'
import type { ILoadingInstance } from 'element-plus/lib/el-loading/src/loading.type'//定义拦截器接口
interface RequestInterceptors<T = AxiosResponse> {
  requestInterceptor?: (cinfig: AxiosRequestConfig) => AxiosRequestConfig
  requestInterceptorCatch?: (err: any) => any
  responseInterceptor?: (cinfig: T) => T
  responseInterceptorCatch?: (err: any) => any
}
// 自定义config类型,继承自axios原本的config类型AxiosRequestConfig,这样自定义类型里面既有AxiosRequestConfig所有的类型,又有额外加上的interceptors类型
interface MyRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
  interceptors?: RequestInterceptors<T>
  showLoading?: boolean
}
​
class Request {
  instance: AxiosInstance //axios实例
  interceptors?: RequestInterceptors //拦截器保存
  loading?: ILoadingInstance //ElementPlus中loading函数保存
  showLoading?: boolean //是否使用loading加载图标
​
  // constructor(config: AxiosRequestConfig) {
  constructor(config: MyRequestConfig) {
    this.instance = axios.create(config)
    //拦截器保存为变量,不保存的话就config.interceptor获取
    this.interceptors = config.interceptors
​
    //1、 每个baseURL实例对象拥有的拦截器
    this.instance.interceptors.request.use(
      this.interceptors?.requestInterceptor,
      this.interceptors?.requestInterceptorCatch
    ) //?.为可选链操作符
    this.instance.interceptors.response.use(
      this.interceptors?.responseInterceptor,
      this.interceptors?.responseInterceptorCatch
    )
​
    //2、所有实例对象共有的拦截器
    this.instance.interceptors.request.use(
      (config) => {
        // console.log('所有请求的请求拦截')
        if (this.showLoading) {
          this.loading = ElLoading.service({
            text: 'Loading',
            spinner: 'el-icon-loading'
          })
        }
        return config
      },
      (err) => {
        return err
      }
    )
    this.instance.interceptors.response.use(
      (res) => {
        // console.log('所有请求的响应拦截')
        this.loading?.close()
        const data = res.data
        if (data.returnCode === '-1001') {
          console.log('请求失败~, 错误信息')
        } else {
          return data
        }
      },
      (err) => {
        // 判断响应状态码,给出失败提示
        // 将loading移除
        this.loading?.close()
​
        // 例子: 判断不同的HttpErrorCode显示不同的错误信息
        if (err.response.status === 404) {
          console.log('404的错误~')
        }
        return err
      }
    )
  }
​
  //定义请求方法
  //泛型T是使用时定义的res的类型
  api<T>(config: MyRequestConfig<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      //3、单个请求独有的请求拦截器(看需求,需要就在定义接口时传入)
      if (config.interceptors?.requestInterceptor) {
        config = config.interceptors.requestInterceptor(config)
      }
      //判断有没有传入showLoading
      if (config.showLoading) {
        this.showLoading = true
      } else {
        //默认不显示loading图标
        this.showLoading = false
      }
      //响应拦截返回的是res.data,已不是AxiosResponse类型,改为<any,T>
      this.instance
        .request<any, T>(config)
        .then((res) => {
          //单个请求独有的数据处理(看需求,需要就在定义接口时传入)
          if (config.interceptors?.responseInterceptor) {
            res = config.interceptors.responseInterceptor(res)
          }
          this.showLoading = false
          resolve(res)
        })
        .catch((err) => {
          this.showLoading = false
          reject(err)
          return err
        })
    })
  }
  // 基于api封装不同的请求,get请求就直接使用 .get()
  get<T>(config: MyRequestConfig<T>): Promise<T> {
    return this.api<T>({ ...config, method: 'GET' })
  }
  post<T>(config: MyRequestConfig<T>): Promise<T> {
    return this.api<T>({ ...config, method: 'POST' })
  }
  put<T>(config: MyRequestConfig<T>): Promise<T> {
    return this.api<T>({ ...config, method: 'PUT' })
  }
  delete<T>(config: MyRequestConfig<T>): Promise<T> {
    return this.api<T>({ ...config, method: 'DELETE' })
  }
  patch<T>(config: MyRequestConfig<T>): Promise<T> {
    return this.api<T>({ ...config, method: 'PATCH' })
  }
}
export default Request
​

入口文件service/index.ts

import Request from './request'
import localCache from '@/utils/cache'
const request = new Request({
  // 这里使用第三种环境变量方法,第二种在config.ts
  baseURL: process.env.VUE_APP_BASE_URL,
  timeout: process.env.VUE_APP_TIME_OUT,
  // 创建实例时传入拦截器,好处在于不同的实例有自己的拦截器,可以实现不同配置(可选)
  interceptors: {
    requestInterceptor(config) {
      // console.log('请求成功')
      const token = localCache.getCache('token')
      if (token) {
        config.headers['Authorization'] = 'Bearer ' + token
      }
      return config
    },
    requestInterceptorCatch(err) {
      // console.log('请求失败')
      return err
    },
    responseInterceptor(res) {
      // console.log('响应成功')
      return res
    },
    responseInterceptorCatch(err) {
      // console.log('响应失败')
      return err
    }
  }
})
export default request
​

请求封装

import request from '../index'enum LoginAPI {
  AccountLogin = '/login',
  UserInfo = '/users/', //   /users/id
  UserMenus = '/role/' //   /role/1/menu
}
​
​
interface Account {
  name: string
  password: string
}
//后端返回的数据格式类型,就是axios封装中的那个泛型 T⭐
interface DataType<T = any> {
  code: number
  data: T //这个data类型不确定(每个接口返回的数据都是不一样的),可以自己定义
}
​
/**
 * 请求的数据data:有的data很简单,可以自己定义它的类型,有的较为复杂,可以使用工具json2ts自动生成(可能会有错,检查),但是那些过于复杂的数据格式,定义类型就很复杂了,对于前端的工作量非常大,建议直接使用any
 */
// data里面的具体类型
interface loginResult {
  id: number
  name: string
  token: string
}
​
​
​
​
//登录请求  请求返回promise对象
export function accountLoginRequest(account: Account) {
  return request.post<DataType<loginResult>>({
    url: LoginAPI.AccountLogin,
    data: account
  })
}
//用户信息
export function userInfoRequestById(id: number) {
  return request.get<DataType>({
      url: LoginAPI.UserInfo + id,
      interceptors: {
          requestInterceptor(config) {
              console.log('配置单个请求的拦截器')
              return config
          }
      },
      showLoading: true
  })
}

6、区分不同环境

在开发中,我们需要根据不同的环境设置不同的环境变量

  • 开发环境:development
  • 生产环境:production
  • 测试环境:test

不同环境的区分:

  • 方式一:不同情况下手动修改不同变量

  • 方式二:根据process.env.NODE_ENV的值进行区分

    process.env.NODE_ENV在不同的环境下有不同的值,他会通过webpack的插件DefinePlugin注入不同的值 在开发环境下的值:development 在生产环境下的值:production 在测试环境下的值:test

    request文件夹下config.js(在网络请求封装文件夹里面配置)

    console.log(process.env.NODE_ENV)
    let base_url=''
    if(process.env.NODE_ENV==="development"){
      base_url='127.0.0.1:3000/api'
    }else if(process.env.NODE_ENV==="production"){
      base_url='127.0.0.1:3000/aaa'
    }else if(process.env.NODE_ENV==="development"){
      base_url='127.0.0.1:3000/test'
    }
    export {base_url}
    
  • 方式三:编写不同环境变量配置文件

    在项目根目录下创建不同的 .env 文件来区分不同的环境配置。 .env:默认环境变量文件,所有环境都会加载这个文件中的变量。 .env.development:开发环境的环境变量文件。 .env.production:生产环境的环境变量文件。

    image.png

    注意:要根据使用的工具来决定,比如Vue CLI创建的项目的环境变量的命名必须以 VITE_APP_ 开头,这样Vue CLI才能正确地将其注入到项目中。我使用的是vite

    VITE_APP_API_URL=https://api.example-development.com
    VITE_APP_AUTH_KEY=your_auth_key
    

    之后可以通过console.log(import.meta.env);来获取变量

    image.png

svg图标的引入和封装

  • 安装插件
  1. 安装 vite-plugin-svg-icons插件,它可以帮助你将 SVG 文件转换为 Vue 组件,并允许你动态更改图标的颜色和大小;
  2. 安装 vite-plugin-svg-icons插件,因为vite-plugin-svg-icons插件插件依赖fast-glob 包,不安装会报错
yarn add vite-plugin-svg-icons -D
yarn add fast-glob --dev
  • 配置 Vite: 在 vite.config.jsvite.config.ts 文件中配置插件,指定 SVG 文件的目录。
//引入
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
...
createSvgIconsPlugin({
  iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
  symbolId: 'icon-[dir]-[name]',
}),

完整文件:

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
import path from "path";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    createSvgIconsPlugin({
      iconDirs: [path.resolve(process.cwd(), "src/assets/iconfont")],
      symbolId: "icon-[dir]-[name]",
    }),
  ],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});
  • 引入 SVG 注册脚本: 在 main.tsmain.js 文件中引入 SVG 注册脚本。
import { createApp } from "vue";
import "./style.css";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import App from "./App.vue";

//引入svg注册脚本
import "virtual:svg-icons-register";
//引入封装好的图标库
import SvgIcon from "@/components/SvgIcon.vue";

const app = createApp(App);
app.use(ElementPlus);
//注册图标库
app.component("SvgIcon", SvgIcon);
app.mount("#app");
  • 添加 SVG 图标: 将 SVG 文件放入指定的目录(如 src/assets/iconfont)。
  • 自定义 SVG 图标组件 创建一个自定义的 SVG 图标组件,以便更灵活地控制图标的样式。因为希望更改图标的颜色,需要将 fill 属性的值设置为 currentColor 或删除该属性,以便通过 CSS 控制颜色。
<template>
    <svg :class="svgClass" aria-hidden="true">
      <use :xlink:href="iconClassName" :fill="color" />
    </svg>
  </template>
  <script setup lang="ts">
  import { computed } from 'vue';
  const props = defineProps({
    iconName: {
      type: String,
      required: true
    },
    className: {
      type: String,
      default: ''
    },
    color: {
      type: String,
      default: '#409eff'
    }
  });
  const iconClassName = computed(() => `#${props.iconName}`);
  const svgClass = computed(() => {
    return `svg-icon ${props.className}`;
  });
  </script>
  <style scoped>
  .svg-icon {
    width: 30px;
    height: 30px;
    position: relative;
    fill: currentColor;
    vertical-align: -2px;
  }
  </style>
  • 使用 SVG 图标: 在 Vue 组件中使用 <svg-icon> 组件,并传递 icon-class 属性来指定图标。
<template>
  <SvgIcon iconName="icon-a" color="#e6899b" />
</template>

image.png