# 项目搭建

# 前言

Vue3 (opens new window) 搭建项目不在像 Vue2 使用vue-cli脚手架创建项目,官方提供了新的构建工具 Vite (opens new window) 来创建项目,并使用Vue3+Vite+Ts 提供新的格式书写与风格指南,在使用之前请先熟悉和阅读 Typescript (opens new window) 和Vue3相关的文档,最好也看一下Vite官网。选择和使用Vue3也是一种趋势,我们就需要更加开放的拥抱Vue3。下面就让我们看看如何使用Vue3+Vite+Ts+Arco搭建一个后台管理系统的。

# 一、构建项目

Vue3官方推荐使用Vite来构建项目,而不再使用webpack,其对应的脚手架也不再使用vue-cli,虽然vue-cli也可以构建过渡版。这里针对Vite的生态还不是很完善,所以选择项目构建时要有所区分。

使用Vite构建项目Vue官方和Vite都提供了构建命令,两者构建项目都可以,区别就是Vue官方提供的构建命令更针对与Vue开发,我们使用Vue官方提供的构建命令就可以了。

# 1、创建项目

Vue3创建项目只需安装好Node后直接执行创建项目的命令即可,选择基于TS+组合式API进行开发,不再使用选项式。

npm create vue@latest

执行后,根据提示进行选择,把TS选项选上。

√ Project name: ... vue-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)
√ Add ESLint for code quality? ... No / (Yes)
Scaffolding project in ./<your-project-name>...
Done.

前几个选项都默认安装就行了,而Eslint和Prettier选项对于没有严格要求的项目可以直接选择Yes,它只会进行简单的代码检查,不会有严格的风格要求。对于团队来说不建议使用这种,默认选择No不添加,在项目创建好后手动配置Eslint来满足需要的风格指南。

> cd vue-project
> npm install
> npm run dev

# 2、项目结构设计

项目目录创建好后,构建工程化如下。

├── public                   # 资源文件
│   └── favicon.ico          
├── src
│   ├── api                  # Api ajax 等
│   ├── assets               # 本地静态资源
│   │   ├── images           # 项目图片
│   │   ├── css              # reset、common样式
│   │   ├── font             # 字体库
│   │   ├── iconfont         # iconfont库
│   │   └── less/sass        # 公共less/sass文件
│   ├── components           # 业务公共组件
│   ├── config               # 基础配置
│   ├── constants            # 常量文件
│   ├── directive            # 自定义指令
│   ├── hooks                # hook函数
│   ├── json                 # json文件
│   ├── layouts              # 基础布局
│   ├── mock                 # mock假数据
│   ├── router               # Vue-Router
│   ├── stores               # pinia
│   ├── types                # 声明文件
│   ├── utils                # 工具库
│   ├── views                # 业务页面入口和常用模板
│   ├── App.vue              # Vue 模板入口
│   └── main.ts              # Vue 入口 TS
├── .env                     # 环境配置文件
├── .env.development         
├── .eslintignore            # eslint忽略文件
├── .eslintrc.cjs            # eslint配置
├── .gitignore               # git忽略
├── env.d.ts                 # 全局声明文件
├── .gitignore               # git忽略
├── index.html               # Vue 入口模板
├── package.json             # 包管理
├── README.md
├── tsconfig.app.json        # ts配置
├── tsconfig.json            # ts配置
├── tsconfig.node.json       # ts配置
└── vite.config.ts           # vite配置

# 3、IDE支持

推荐使用 Visual Studio Code 进行开发,在VSCode中通过Volar (opens new window) TypeScript Vue Plugin (opens new window)两个插件对代码进行类型检查,这俩插件现已弃用,在VSCode中通过Vue - Official (opens new window) 插件进行代码检查。在开发中可以很大程度的帮助检查和提示类型出错,如果想要更严格的类型检查,可以通过Eslint+TS相关的代码检查插件进行控制。

新版本只需安装 Vue - Official 插件即可,Volar Takeover模式也无需开启,把TypeScript and JavaScript Language Features服务启动。

  • Volar是官方的 VSCode 扩展,提供了 Vue 单文件组件中的 TypeScript 支持,Volar 取代了我们之前为 Vue 2 提供的官方 VSCode 扩展 Vetur。如果之前已经安装了 Vetur,需要在Vue3项目中禁用。
  • TypeScript Vue Plugin针对*.vue 文件进行类型检查。

# 开启Volar Takeover 模式

在添加Volar插件后~~,Volar 提供了一个叫做“Takeover 模式”的功能。在这个模式下,Volar 能够使用一个 TS 语言服务实例同时为 Vue 和 TS 文件提供支持,要开启这个模式需要在VSCode中禁用默认的TS语言服务。~~

  1. 在当前项目的工作空间下,用Ctrl + Shift + P (mac:Cmd + Shift + P) 唤起命令面板。
  2. 输入built,然后选择“Extensions:Show Built-in Extensions”。
  3. 在插件搜索框内输入typescript (不要删除 @builtin 前缀)。
  4. 点击“TypeScript and JavaScript Language Features”右下角设置,然后选择禁用。
  5. 重新加载工作空间。Takeover 模式将会在你打开一个 Vue 或者 TS 文件时自动启用。

# 二、Vite相关配置

以前基于webpack的配置现在全部改为基于Vite的配置,所以很多配置都有所改变,最主要的改变就是环境变量、静态资源、config配置等。

# 1、vite.config.ts

Vue3中关于项目的配置全部写在vite.config.ts中,很多基础配置和以前的vue.config.js差不多,具体API可以参考 配置 Vite (opens new window)

// vite.config.ts
import path from 'path'
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import eslintPlugin from 'vite-plugin-eslint'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'

// https://vitejs.dev/config/
export default defineConfig({
  // base: '/',
  // 本地服务启动配置、跨域配置
  // server: {
  //   host: 'localhost',
  //   port: 8080,
  //   proxy: {
  //     '/api': {
  //       target: 'http://192.168.100.14:3351',
  //       ws: false,
  //       changeOrigin: true
  //     }
  //   }
  // },
  
  // 插件配置
  plugins: [
    vue(),
    vueJsx(),
    // 配置vite在运行的时候自动检测eslint规范
    eslintPlugin({
      include: ['src/**/*.ts', 'src/**/*.js', 'src/**/*.vue', 'src/*.ts', 'src/*.js', 'src/*.vue']
    }),
    // 自定义组件名称
    VueSetupExtend()
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  // less、sass配置
  css: {
    preprocessorOptions: {
      // 配置less全局使用
      less: {
        modifyVars: {
          hack: `true; @import (reference) "${path.resolve('src/assets/less/main.less')}";`,
        },
        javascriptEnabled: true
      }
    }
  }
})

# 2、环境变量

Vite在环境变量.env等文件里配置变量统一使用 VITE_ 进行开头。

// .env
VITE_ENV=production
VITE_APP_API_BASE_URL=/api

// .env.development
VITE_ENV=development
VITE_APP_API_BASE_URL=http://192.168.100.1

获取时通过一个特殊的 import.meta.env 对象上暴露环境变量。不在是以前的process.env对象。

// 创建 axios 实例
const request: AxiosInstance = axios.create({
  // API 请求的默认前缀
  baseURL: import.meta.env.VITE_APP_API_BASE_URL,
  timeout: 1000 * 60 * 10 // 请求超时时间
})

// 判断环境是开发环境时
if (import.meta.env.VITE_ENV === 'development') {
  console.log(1111)
}

在HTML中也能使用环境变量,import.meta.env 中的任何属性都可以通过特殊的 %ENV_NAME% 语法在 HTML 文件中使用。

<h1>当前模式: %VITE_ENV %</h1>
<p>当前接口地址: %VITE_APP_API_BASE_URL%</p>

# 3、静态资源处理

在HTML中引入assets和public是和以前一样的,主要是在js中动态引入方式不同,以前通过requier,现在需要通过 new URL() 方式引入。

<template>
  <div v-for="(src, idx) in imageSrc" :key="idx">
    <img :src="src" style="width: 100%" />
  </div>
</template>
<script setup lang="ts">
const imageSrc = [
  new URL('@/assets/images/banner01.png', import.meta.url).href,
  new URL('@/assets/images/banner02.png', import.meta.url).href,
  new URL('@/assets/images/banner03.png', import.meta.url).href,
  new URL('@/assets/images/banner04.png', import.meta.url).href,
  new URL('@/assets/images/banner05.png', import.meta.url).href
]
const imgUrl = new URL('./img.png', import.meta.url).href

</script>
<style lang="less" scoped>
</style>

图片地址不能直接支持动态参数,不能整个使用变量替换,仅支持模板字符串写法。

function getImageUrl(name) {
  return new URL(`./dir/${name}.png`, import.meta.url).href
}

# 4、使用插件

plugins插件配置就相当于以前webpack的各种loader一样,针对各种文件或者内容进行处理,然后在打包,Vite虽然构建速度快,但是它的插件生态却不如webpack完善。插件在vite.config.ts中进行引入和配置。

import path from 'path'
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'

// 引入插件
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import eslintPlugin from 'vite-plugin-eslint'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'

// https://vitejs.dev/config/
export default defineConfig({
  // 插件配置
  plugins: [
    vue(),
    vueJsx(),
    // 配置vite在运行的时候自动检测eslint规范
    eslintPlugin({
      include: ['src/**/*.ts', 'src/**/*.js', 'src/**/*.vue', 'src/*.ts', 'src/*.js', 'src/*.vue']
    }),
    // 自定义组件名称
    VueSetupExtend()
  ]
})

# 三、TS使用指南

在项目中全部使用ts文件进行开发,不在使用js。开发TS项目时,需要配置TS相关的内容,Vite为我们预先在tsconfig.json文件中配置了,我们只需在其中修改即可。并且VScode的Volar和Eslint都提供了对TS进行代码校验,在开发中需要进行类型校验的都会给与提示和补全,根据这些提示进行修改可以很大程度帮助我们解决报错和标红。参考链接:TS项目配置 (opens new window)

# 1、ts配置文件

tsconfig.json就是ts的配置文件,放在根目录,它指定了用来编译该项目的根文件和编译选项,所有ts相关的配置都放在其中,在Vite中配置文件被拆出tsconfig.app.json和tsconfig.node.json两个文件进行引入,在 tsconfig.app.json中是基础配置。

在tsconfig.app.json配置文件中 include 和 exclude 选项可以指定该项目需要使用ts进行编译的文件和不需要编译的文件,compilerOptions中是其他相关配置。如果在项目中新建的文件无法被ts所识别第一考虑就是此配置中添加include。

{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/**/*.json", "src/**/*.d.ts", "./.eslintrc.cjs", "./vite.config.ts"],
  "exclude": ["src/**/__tests__/*"],
  "compilerOptions": {
    "composite": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

# 2、声明文件

在开发时,我们可以为变量或者函数定义类型和返回值进行类型约束,定义什么就约束什么,这是针对当前所开发的代码,如果像是第三方库和插件、全局变量和方法或者开发项目的宿主环境,在整个项目中都有使用和贯穿,使用它的变量和方法肯定需要进行类型验证,我们无法在每个使用的代码中为其声明类型,这时就需要通过声明文件对其变量或方法进行类型声明,无论在哪使用都能进行类型检查。

ts的声明文件是以 .d.ts 为后缀的文件,开发者在声明文件中编写类型声明,ts根据声明文件的内容进行类型检查,然而在有些文件或者第三方库中是没有类型声明的,没有支持ts的写法,在使用变量、调用函数、实例化类时ts识别不了,无法通过类型检查,就会有一大堆报错。所以要在声明文件中编写第三方库的类型声明,让ts可以正常地进行类型检查,才能获得对应的代码补全、接口提示等功能。

一般流行的第三方库都支持ts的类型声明,安装时如果默认没有声明文件,可以去

@types organization (opens new window) 上去搜对应的声明文件,搜索加上@types,通过 npm install @types/模块名 -D 去下载其社区为其声明的文件,声明文件的包在@/node_modules/@types里,要是一些比较冷门的库可能没有,我们就要手动为其创建声明文件了,也可以声明好后发布到npm插件库中。

参考链接:TS声明文件 (opens new window)

添加声明文件或全局公共类型标注文件,一般约定在src目录下创建types文件夹来保存文件,创建好后,在项目根目录下有一个 env.d.ts 文件,这个文件是全局声明文件的入口,可以在此文件中直接引入其他 .d.ts 文件,然后就会全局生效。env.d.ts在tsconfig.json中通过include引入解析。

// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference types="vite/client" />
/// <reference types="./src/types/global.d.ts" />
/// <reference types="./src/types/api.d.ts" />

/**
 * 全局声明文件
 * 在此自定义一些全局声明或者引入声明文件
 * 在此通过reference引入types中的声明文件
 * <reference types="./src/types/api.d.ts" />
 * */

// store声明
// declare module 'store'

如果不想在env.d.ts 文件中一个一个引入,也可以直接在tsconfig配置include中直接通过匹配符直接匹配解析全部的.d.ts文件。

// 通过通配符匹配src目录下的文件
{
  "include": [ "src/**/*.d.ts"],
}

# 3、搭配Vue使用

<script setup> 使用ts只需要在上面添加 lang="ts" 即可。

<template>
  <!-- 启用了类型检查和自动补全 -->
  {{ count.toFixed(2) }}
</template>
<script setup lang="ts">
// 启用了 TypeScript
import { ref } from 'vue'
const count = ref(1)
</script>

为ref和reactive添加类型校验,定义基础数据类型时一般会自动推导,可以不用为其标注类型,但定义引用类型时最好是加上类型标注。针对是使用ref还是reactive,网上争论还是很多,因为reactive的局限性比较多,赋值或解构操作都会使其失去响应性,一般我们推荐以ref类型居多,reactive辅助。在定义基础类型和数组我们使用ref来定义,对象的话看情况,如果这个对象会被重新赋值则还是继续使用ref,如果只是维护一些字段就使用reactive,但不要为了方便把所有的数据都塞到一个reactive对象中去,组合式API就是为了拆分细粒度,不同的逻辑放在不同位置,全都塞到一起反而本末倒置,这也是vue官方所不推崇的。

<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { Ref } from 'vue'
const title = ref<string>('首页')
const status: Ref<undefined | number> = ref('2020')

// 简写
const title = ref('首页')
const status = ref<undefined | number>(0)

// 不给初始值会自动转为包含 undefined 的联合类型
// 推导得到的类型:Ref<number | undefined>
const status = ref<number>()

// 定义对象
interface StateData {
  name: string;
  count: number;
  type: undefined | '';
  date?: string;
  [key: string]: any
}
const state = reactive<StateData>({
  name: 'xxx',
  count: 0,
  type: undefined,
  data: '2023-01-01',
  status: 0
})

// 定义数组
const tableData = ref<StateData[]>([])
tableData.push(state)
</script>

如果定义的数据类型在多个文件中使用,我们可以把它提出来,放在全局公共的types文件中,如果在当前页面多个组件中使用,可以在index.vue目录创建 types.ts 文件来保存公共的数据类型进行导出,就像局部组件和公共组件一样,然后在通过import type 进行引入。

// types.ts

// 筛选条件
export interface FilterData {
  name: string
  age: number
  class: string
  type: undefined | ''
  date: string
  status: undefined | ''
}

// table数据
export interface TableInfo {
  key: string
  name: string
  salary: number
  address: string
  email: string
  type: number | undefined
}

然后在页面中引入

import type { FilterData, TableInfo } from './types'

# 4、获取插件声明文件

在使用插件提供的属性或方法时,或者定义插件所需要的数据格式,都会进行ts类型校验,这时需要引入插件所提供的类型进行声明,如果类型不存在我们需要自定义类型声明,也可以根据已有的类型声明导入组装成新的声明。

一般安装@types/xxx后都会有相关的类型提示,直接输入对应的名称就可以看到,如果没有就去node_modules相关的包里去找,搜索对应的类型,无法引入的就全复制出来自己定义声明文件。

<template>
  <a-form ref="formRef" :model="formData" :label-col-props="{ span: 4 }" :wrapper-col-props="{ span: 18 }">
    <a-form-item label="Name" field="name" :validate-trigger="['blur']">
      <a-input v-model="formData.name" :max-length="50" show-word-limit placeholder="请输入" allow-clear />
    </a-form-item>
  </a-form>
</template>
<script setup lang="ts">

import { reactive, ref, getCurrentInstance, onMounted } from 'vue'
// 引入Form表单类型
import type { FormInstance } from '@arco-design/web-vue/es/form'

// 使用引入的类型定义ref
const formRef = ref<FormInstance>()

// 确认添加
const confirmSubmit = () => {
  // 识别插件类型并调用方法
  formRef.value?.validate(errors => {
    if (!errors) {} 
  })
}
</script>

自定义一些常见的声明

// 申明外部 npm 插件模块
declare module 'splitpanes'
declare module 'js-cookie'
declare module '@wangeditor/editor-for-vue' {
  import { DefineComponent } from 'vue'
  const Editor: DefineComponent<{}, {}, any>
  const Toolbar: DefineComponent<{}, {}, any>
  export { Editor, Toolbar }
}

// 声明一个模块,防止引入文件时报错
declare module '*.json'
declare module '*.png'
declare module '*.jpg'
declare module '*.scss'
declare module '*.ts'
declare module '*.js'

// 声明文件,*.vue 后缀的文件交给 vue 模块来处理
declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

// 声明文件,定义全局变量
/* eslint-disable */
declare interface Window {
  isMobile: boolean
}

# 5、代码风格指南

<script setup lang="ts" name="Home">
/**
 * @desc 注释
 * @author xxx
 * */

import { ref, reactive, onMounted } from 'vue'

import { useRouter } from 'vue-router'
import { useAppStore } from '@/stores/modules/app'
import useMouse from '@/hooks/mouse'
import useGlobalProperties from '@/hooks/globalProperties'

import type { FormInstance } from '@arco-design/web-vue/es/form'
import type { UserInfo } from '../types'

import ArcoIcon from '@/components/ArcoIcon'
import Ellipsis from '@/components/Ellipsis/index'

const router = useRouter()
const appStore = useAppStore()
const { global } = useGlobalProperties()

// 接收参数
const props = defineProps<{
  title: string
  likes?: number
}>()

// 定义事件
const emit = defineEmits<{
  CLOSE_EVENT: [id?: number, name: string]
}>()

const formRef = ref<FormInstance>()
const title = ref<string>('首页')
const msg = ref<string>('hello world!')
const userInfo = reactive<UserInfo>({
  name: 'xxx',
  age: 18
})



// 增删改
const updateDialog = reactive({
  visible: false,
  id: ''
})
const addData = () => {
  updateDialog.visible = true
}
const editData = (id: string) => {
  updateDialog.visible = true
  updateDialog.id = id
}
const delData = (id: string) => {
  updateDialog.visible = true
  updateDialog.id = id
}



// 其他功能模块
...

// 其他功能模块
...
</script>

# 四、代码检查

添加Eslint代码检查

# 五、项目配置

# 1、Router引入

Router使用

2、Pinia引入

Pinia使用

3、Aixos封装

Axios请求封装

# 4、Less引入

安装后,在vite.config.ts中进行配置。

import path from 'path'
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'

// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  css: {
    preprocessorOptions: {
      // 配置less全局使用
      less: {
        modifyVars: {
          hack: `true; @import (reference) "${path.resolve('src/assets/less/main.less')}";`,
        },
        javascriptEnabled: true
      }
    }
  }
})

# 5、Mock引入

添加mock假数据基本上和Vue2一样,只要把js文件改成ts,然后返回接口修改成api接口对应的返回格式,对应的类型校验修改一下就可以了。

安装mockjs对应的声明文件:

npm i --save-dev @types/mockjs

Mock假数据

# 6、获取全局实例

获取全局实例通过 getCurrentInstance 方法进行获取Vue实例上的方法,在每个组件中获取都要重新引入一遍太麻烦,我们把它提取出来放到hook中,在需要使用的文件引入调用就可以。

// hooks/globalProperties.ts
/**
 * 全局hook
 * 获取Vue实例上的全局属性
 * @example 调用示例
 * import useGlobalProperties from '@/hooks/globalProperties'
 * const { global } = useGlobalProperties()
 * global?.$message.success('操作成功')
 * */

import { getCurrentInstance } from 'vue'

export default function useGlobalProperties() {
  const instance = getCurrentInstance()
  // const global = instance?.proxy
  const global = instance?.appContext.config.globalProperties
  return { global }
} 

# 7、Event Bus使用

Vue2 通过再创建一个Vue实例当做桥梁来进行组件之间的相互传参,Vue3不再支持此方法,我们通过引入mitt插件来实现兄弟组件之间的事件传递。

安装:

npm install --save mitt

在 src 文件下创建 mitt 文件,然后分别创建index.ts 和 types.ts,types主要用于对提交事件进行类型标注。

// mitt/index.ts
/**
 * 配置mitt公共组件传参
 * @desc 统一管理全局mitt事件,如不需要可注释
 * @desc 定义全局唯一Key,以MITT_开头,以区分其他常量
 * @example 调用示例
 * global?.$Bus.emit('MITT_GET_USERINFO', { name: 'xxx' })
 * global?.$Bus.on('MITT_GET_USERINFO', (params) => { })
 * global?.$Bus.off('MITT_GET_USERINFO')
 * */

import mitt from 'mitt'
import type { Events } from './types'

// const emitter = mitt()
const emitter = mitt<Events>()

export default emitter

在types中定义事件名称,对所有的事件名进行统一管理。

/**
 * mitt传参类型定义
 * @desc 统一管理全局mitt事件,并进行类型标注
 * @desc 定义全局唯一Key,以MITT_开头,以区分其他常量
 * */

interface UserInfo {
  name: string
}

export type Events = {
  'MITT_GET_USERINFO': UserInfo,
  'MITT_GET_NUMBER'?: number
}

在main.ts中引入:

// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import './assets/main.css'

import emitter from '@/mitt'

// TS注册
// 全局属性必须要扩展ComponentCustomProperties类型才能获得类型提示
declare module 'vue' {
  export interface ComponentCustomProperties {
    $Bus: typeof emitter
  }
}

const app = createApp(App)

app.use(createPinia())
app.use(router)
// 挂载到全局属性上
app.config.globalProperties.$Bus = emitter

app.mount('#app')

组件中使用:

<script setup lang="ts">
// A组件发送事件
import useGlobalProperties from '@/hooks/globalProperties'
const { global } = useGlobalProperties()
const sendEmit = () => {
  global?.$Bus.emit('MITT_GET_USERINFO', { name: 'xxx' })
}
</script>



<script setup lang="ts">
// B组件接收事件
import { ref, onMounted, onUnmounted } from 'vue'
import useGlobalProperties from '@/hooks/globalProperties'
const { global } = useGlobalProperties()

// 接收事件
const sendMsg = ref({
  name: ''
})

onMounted(() => {
  global?.$Bus.on('MITT_GET_USERINFO', (params) => {
    sendMsg.value = params
  })
})

// 卸载事件
onUnmounted(() => {
  global?.$Bus.off('MITT_GET_USERINFO')
})
</script>

# 8、依赖注入

依赖注入使用时只需要注意注入的key是唯一的就可以,并且还要提供类型标注。我们在 src 文件下创建 provide 文件统一来管理注入的key。

// provide/index.ts
/**
 * 依赖注入定义唯一Key和声明类型
 * @desc 定义全局唯一key,在provide和inject中引入使用
 * @desc 定义key统一以PROVIDE_开头,以区分其他常量
 * @example 调用示例
 * import { PROVIDE_STRING } from '@/provide'
 * provide(PROVIDE_STRING, 'hello world')
 * const foo = inject(PROVIDE_STRING)
 * */

import type { InjectionKey } from 'vue'
import type { UserInfo } from './types'

// 定义一个类型为string的key
export const PROVIDE_STRING = Symbol('STRING_KEY') as InjectionKey<string>

// 定义一个类型为UserInfo的key
export const PROVIDE_USER_INFO = Symbol('USER_INFO') as InjectionKey<UserInfo>

// ...其他key

在types文件中定义注入的的类型,然后在index中引入使用

// provide/types.ts
/**
 * 依赖注入类型声明
 * */
export type id = string | number

export interface UserInfo {
  id: number
  // name: string
  // role: string
  [key: string]: any
}

在组件中使用:

<script setup lang="ts" name="Home">
// 父组件
import { provide } from 'vue'
import { PROVIDE_STRING } from '@/provide'

// 依赖注入使用
provide(PROVIDE_STRING, 'hello world123')
</script>



<script setup lang="ts" name="Home">
// 子组件
import { inject } from 'vue'
import { PROVIDE_STRING } from '@/provide'

// 获取依赖注入
const foo = inject(PROVIDE_STRING)
</script>

# 9、自定义全局指令

在directive文件夹创建modules来保存指令文件,然后创建index.ts文件引入指令文件,通过插件统一进行注册,最后在main.ts中使用。

自定义指令

// directive/modules/index.ts
/**
 * 自动获取焦点指令
 * @description 打开页面时输入表单自动获取焦点
 * @author changz
 * @param {xxx} - xxx
 * @example
 * <a-input v-focus></a-input>
 * */

import type { DirectiveBinding, VNode } from 'vue'

export default {
  mounted(el: HTMLInputElement, binding: DirectiveBinding, vnode: VNode) {
    if (el.tagName === 'input' || el.tagName === 'textarea') {
      el.focus()
    } else {
      const inputDom = el.querySelector('input') ?? el.querySelector('textarea')
      inputDom?.focus()
    }
  }
}

在index.ts引入指令并导出

// directive/index.ts
/**
 * @description 定义多个全局自定义指令
 * @author changz
 * @example 在main.js引入之后use挂载
 * */

import type { App, Directive } from 'vue'
// 导入自定义指令
import focus from './modules/focus'
import watermark from './modules/watermark'
import permission from './modules/permission'

export default {
  install(Vue: App) {
    Vue.directive('focus', focus as Directive)
    Vue.directive('watermark', watermark as Directive)
    Vue.directive('permission', permission as Directive)
  }
}

在main.ts中引入

import { createApp } from 'vue'

import App from './App.vue'
import router from './router'
import store from './stores'
import directive from './directive'

const app = createApp(App)

app.use(store)
app.use(router)
app.use(directive)
app.mount('#app')

# 10、自定义组件名

Vue3默认是以文件名当作组件的name的,如果我们想自定义组件name,需要使用插件 vite-plugin-vue-setup-extend (opens new window) 来帮助我们完成。

安装:

npm i vite-plugin-vue-setup-extend -D

在vite.config.ts中引入:

import path from 'path'
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'

export default defineConfig({
  plugins: [
    vue(),
    vueJsx(),
    // 自定义组件名称
    VueSetupExtend()
  ]
})

在组件上定义name:

<template>
</template>

<!-- If you want the include property of keep-alive to take effect, you must name the component -->
<script setup lang="ts" name="Home">
</script>

<style lang="less" scoped>
</style>

# 六、添加Arco Desing

添加ArcoDesign

# 七、权限控制

权限控制