Vue3+Vite+Ts project combat 03 Initialize Element Plus, API and component automatic import, ElMessage component use, Vue global properties, Axios, environment variables, cross-domain

Initialize Element Plus

Element Plus is an upgraded version of Element UI for Vue 3.

Install and configure automatic on-demand import

# 安装
npm install element-plus --save

It is recommended to use on-demand import . The official recommendation is to use unplugin-vue-componentsand unplugin-auto-importthese two plug-ins to implement automatic import to make up for some shortcomings of on-demand import (manual registration of components, etc.).

# 安装插件
npm install -D unplugin-vue-components unplugin-auto-import

Configure Vite:

// vite.config.ts
...
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import {
    
     ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
    
    
  plugins: [
    ...
    // ElementPlus 自动导入
    AutoImport({
    
    
      resolvers: [ElementPlusResolver()]
    }),
    Components({
    
    
      resolvers: [ElementPlusResolver()]
    })
  ],
  ...
})

test:

<el-button type="primary">Primary</el-button>

globalization

Element Plus components use English by default, such as the date component:

insert image description here

If you want to use other languages ​​(such as Chinese), you need to configure internationalization globally.

The complete import method can be configured through options during registration locale.

On-demand import needs to be configured using the officially provided Vue components:

<!-- src\App.vue -->
<template>
  <el-config-provider :locale="locale">
    <router-view />
  </el-config-provider>
</template>

<script setup lang="ts">
import locale from 'element-plus/lib/locale/lang/zh-cn'
</script>

Effect (you need to refresh the page for the configuration to take effect):

insert image description here

icon

If you want to use the Element Plus icon directly in the project like the official case, you need to register the component globally. The official auto-imported plug-in is still under development, and the current manual global registration:

// src\plugins\element-plus.ts
import {
    
     App } from 'vue'
import * as ElIconModules from '@element-plus/icons-vue'

export default {
    
    
  install(app: App) {
    
    
    // 批量注册 Element Plus 图标组件
    // 或者自定义 ElIconModules 列表
    for (const iconName in ElIconModules) {
    
    
      app.component(iconName, (ElIconModules as any)[iconName])
    }
  }
}

// src\main.ts
import {
    
     createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import elementPlus from './plugins/element-plus'

// 加载全局样式
import './styles/index.scss'

createApp(App)
  .use(router)
  .use(store)
  .use(elementPlus)
  .mount('#app')

API and component auto-import

The plug-ins for automatic import and installation of Element Plus are not only used for itself, in fact, these two plug-ins can be applied to various frameworks and libraries.

unplugin-vue-components

The unplugin-vue-components plugin is used to automatically identify the components used in the Vue template, automatically import and register on demand.

It provides parsers for several configurable UI libraries, see the "Importing from UI Libraries" section.

A file will be generated in the TypeScript project components.d.tsto automatically supplement and update the type declaration file of the component.

**Note: **The plug-in automatically recognizes the components used in the template templatecomponents.d.ts , you can check to confirm whether it has been recognized.

unplugin-auto-import

The unplugin-auto-import plugin can automatically import common APIs of configuration libraries on demand in Vite, Webpack, Rollup and esbuild environments, such as Vue's, refwithout manual work import.

You can importsconfigure the automatically imported API (preset or custom rules) through the configuration item, or resolversconfigure the parser of the component library (such as Element Plus).

In a project that supports TypeScript, a file will be generated in the root directory of the project after the plug-in is installed auto-imports.d.ts. When the configuration is automatically imported, the type declaration corresponding to the API of the configuration library will be automatically supplemented.

Note: auto-imports.d.tsThe file will be verified by ESLint by default, and an error will be reported <变量> is defined but never used.. You can ignore the verification of the file by ESLint.

Configure API auto-import

Configure API auto-import for Vue, Vue Router and Pinia:

// vite.config.ts
...
import AutoImport from 'unplugin-auto-import/vite'
...

// https://vitejs.dev/config/
export default defineConfig({
    
    
  plugins: [
    ...
    AutoImport({
    
    
      imports: [
        // presets
        'vue',
        'vue-router',
        'pinia'
      ],
      eslintrc: {
    
    
        enabled: true,
        filepath: './.eslintrc-auto-import.json',
        globalsPropValue: true
      },
      // ElementPlus 自动导入
      resolvers: [ElementPlusResolver()]
    }),
    ...
  ],
  ...
})

After the save takes effect, auto-imports.d.tsthe content will be automatically filled, and .eslintrc-auto-import.jsonthe eslint global variable configuration will be generated in the project root directory.

These two files need to be manually added to the TypeScript and ESLint configuration files:

// tsconfig.json
{
    
    
  ...
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "auto-imports.d.ts"],
  "references": [{
    
     "path": "./tsconfig.node.json" }]
}

// .eslintrc.js
module.exports = {
    
    
  ...
  extends: [
    ...
    // unplugin-auto-import
    './.eslintrc-auto-import.json'
  ],
  ...
}

Ignores auto-imports.d.tsESLint validation.

# .eslintignore
auto-imports.d.ts

It is recommended to restart the editor for the configuration to take effect.

Now you can directly use the API of Vue, Vue Router and Pinia without manual importwork.

For example, you can annotate the imports of the following APIs:

<!-- src\layout\AppHeader\Breadcrumb.vue -->
...

<script setup lang="ts">
// import { useRouter } from 'vue-router'
// import { computed } from 'vue'

const router = useRouter()

const routes = computed(() => {
      
      
  return router.currentRoute.value.matched.filter(item => item.meta.title)
})

</script>

// src\store\index.ts
// import { defineStore } from 'pinia'

const useStore = defineStore('main', {
    
    
...
})

export default useStore

// src\main.ts
// import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// import { createPinia } from 'pinia'
import elementPlus from './plugins/element-plus'

...

Notice:

  1. Not all APIs , such as Vue Router's createRouterwill not be imported. For specific APIs that can be automatically imported, refer to unplugin-auto-import/src/presets
  2. .eslintrc-auto-import.jsonIf you do not need to add configuration after generating the file, it is recommended enabled: trueto set to false, otherwise this file will be generated every time.

Separate reference to ElementPlus components

The principle of automatic on-demand import is to <template>automatically import by identifying the components used in , which leads to the fact that if you use ElMessagesuch components that directly call methods in JS, the plug-in will not recognize and complete the automatic import.

For example:

  • <script>Used in Vue single-file components
  • Used in Axios interceptor

So such components need to be manually operated:

  • Manually importimport components (complete import also requires manual import)
  • Import style files manually ( element-plus/theme-chalk/xxx.css)

For example commonly used ElMessagecomponents:

import {
    
     ElMessage } from 'element-plus'
import 'element-plus/theme-chalk/el-message.css'

ElMessage.success('成功使用')

But there is still a problem with this, because some components also use other Element components, such as the ElMessageBoxconfirmation button in uses ElButtonthe component, although the rendering is successful, but because it is not automatically imported through the plug-in, there is no style.

insert image description here

If it is used in a Vue single-file component and used in the template <el-button>, it will trigger automatic import of the component's style file.

Therefore, it is recommended to import on-demand and still import complete style files to avoid such boundary problems.

// src\plugins\element-plus.ts
import {
    
     App } from 'vue'
import * as ElIconModules from '@element-plus/icons-vue'
import 'element-plus/theme-chalk/index.css'

export default {
    
    
  install(app: App) {
    
    
    // 批量注册 Element Plus 图标组件
    // 或者自定义 ElIconModules 列表
    for (const iconName in ElIconModules) {
    
    
      app.component(iconName, (ElIconModules as any)[iconName])
    }
  }
}

import {
    
     ElMessage } from 'element-plus'
// 不再需要单独引入组件样式
// import 'element-plus/theme-chalk/el-message.css'

ElMessage.success('成功使用')

Vue global properties (globalProperties)

Official documentation:

In Vue 2, variables and methods that can be accessed Vue.prototypeglobally ( ) for all Vue instances can be set at once .this

Vue 3 uses the object on the root instance to register the global properties accessed by all component instances in app.config.globalPropertiesthe instance ( ), instead of Vue 2's method of modifying all root instances.app

Register ElementPlus message prompt component globally

If Element Plus is fully introducedapp.config.globalProperties , some global methods of components (such as , , ElMessageetc. $message) ElMessageBoxwill be added automatically.$msgbox$alert

However, if you use the on-demand import method, when using this type of component, you need to manually import the component and component style in the used module.

In order to reduce repeated imports in Vue components, they can be registered as global variables of the Vue instance (the variable name refers to the name of the full import registration):

// src\plugins\element-plus.ts
import {
    
     App } from 'vue'
import * as ElIconModules from '@element-plus/icons-vue'
import {
    
     ElMessage, ElMessageBox } from 'element-plus'
import 'element-plus/theme-chalk/index.css'

export default {
    
    
  install(app: App) {
    
    
    // 批量注册 Element Plus 图标组件
    // 或者自定义 ElIconModules 列表
    for (const iconName in ElIconModules) {
    
    
      app.component(iconName, (ElIconModules as any)[iconName])
    }

    // 将消息提示组件注册为全局方法
    app.config.globalProperties.$message = ElMessage
    app.config.globalProperties.$msgBox = ElMessageBox
  }
}

use global variables

Can be accessed via global variables in the options API this.<globalProperty>, or used directly in templates:

<template>
  <button @click="$message.success('可以在模板中直接使用')">
    提示
  </button>
</template>
<script lang="ts">
export default defineComponent({
    
    
  mounted() {
    
    
    this.$message.success('Options API 成功使用')
  }
})
</script>

Use global variables in setup

The official does not introduce how to use global variables in setup()and .<script setup>

Because app.config.globalPropertiesthe purpose of is to replace Vue.prototypethe use of 2.x, with the update of the global API, there will no longer be a one-time Vue global configuration, but a separate configuration for each root instance .

This is not intended to be used in setup, you should import the content directly or setupuse it in provide/inject.

Refer to Issues:

provide/inject mode

<!-- 父级组件,如 App.vue -->
<script setup>
import {
      
       ElMessage } from 'element-plus'

provide('$message', ElMessage)
</script>

<!-- 子孙组件 -->
<script setup>
const $message = inject('$message')

onMounted(() => {
      
      
  $message.success('setup - provide/inject 成功使用')
})
</script>

How to get instance in setup (not recommended)

In another way, Vue exposes an API that getCurrentInstance()can access internal component instances, through which the global variables of the root instance can be accessed:

<script setup lang="ts">
const instance = getCurrentInstance()

onMounted(() => {
      
      
  instance.proxy.$message.success('setup - getCurrentInstance() 成功使用')
  // 也可以使用 appContext
  console.log(instance.appContext.config.globalProperties.$message === instance.proxy.$message) // true
})
</script>

<script lang="ts">
export default defineComponent({
      
      
  mounted() {
      
      
    console.log(this.instance.proxy === this) // true
  }
})
</script>

suggestion

There are many ways to use it, and it is recommended to use a safe way, such as direct import or use under the option API.

Global variable TypeScript type declaration

Add type declaration file:

// src\types\global.d.ts
import {
    
     ElMessage, ElMessageBox } from 'element-plus'

declare module 'vue' {
    
    
  // vue 全局属性
  export interface ComponentCustomProperties {
    
    
    $message: typeof ElMessage
    $msgBox: typeof ElMessageBox
  }
}

Package request module based on Axios

npm i axios

basic configuration

// src\utils\request.ts
import axios from 'axios'
import {
    
     ElMessage } from 'element-plus'
// 在 plugins/element-plus.ts 引入了全部组件样式,这里不需额外引入

// 创建 axios 实例
const request = axios.create({
    
    
  baseURL: 'http://localhost:5000/api/admin'
})

// 请求拦截器
request.interceptors.request.use(function (config) {
    
    
  // 统一设置用户身份 token
  return config
}, function (error) {
    
    
  return Promise.reject(error)
})

// 响应拦截器
request.interceptors.response.use(function (response) {
    
    
  // 统一处理接口响应错误,如 token 过期无效、服务端异常等
  if (response.data.status && response.data.status !== 200) {
    
    
    ElMessage.error(response.data.msg || '请求失败,请稍后重试')
    return Promise.reject(response.data)
  }
  return response
}, function (error) {
    
    
  return Promise.reject(error)
})

export default request

All interface requests are src/apiorganized under the directory:

// src\api\common.ts
// 公共基础接口封装
import request from '@/utils/request'

export const demo = () => {
    
    
  return request({
    
    
    method: 'GET',
    url: '/demo'
  })
}

use:

<!-- src\views\login\index.vue -->
<template>
  <div>
    登录
  </div>
</template>

<script setup lang="ts">
import {
      
       demo } from '@/api/common'
import {
      
       onMounted } from 'vue'

onMounted(() => {
      
      
  demo().then(res => {
      
      
    console.log(res.data)
    // {"msg":"ok","status":200,"data":{"title":"Hello World","date":1649412637487}}
  })
})

</script>

The interface type that encapsulates the response data

What is currently obtained resis the response object packaged by Axios. The real data returned by the backend does not have a declared type, so the IDE cannot provide smart prompts, so the type of the response data must be manually declared.

requestResponse data generics are not supported, so send requests that support generics should be used instead request.<method>.

export const demo = () => {
    
    
  return request.get<{
    
    
    status: number
    msg: string
    data: {
    
    
      title: string
      date: number
    }
  }>('/demo')
}

Now the IDE can automatically prompt res.datathe fields under .

But each interface will return status, msgand datafields. In order to avoid repeated declarations, they can be encapsulated into an interface type (interface), and datadefined as a generic type:

interface ResponseData<T = any> {
    
    
  status: number
  msg: string
  data: T
}

export const demo = () => {
    
    
  return request.get<ResponseData<{
    
    
    title: string
    date: number
  }>>('/demo')
}

Encapsulate generic request methods

Now accessing the response data's datafields needs to be passed res.data.data.title.

If you want to be more concise, for example res.title, you can return after the request .then(res => res.data.data).

So this action must be added after each request.

Usually we process it in the axios interceptor, but request.get()the return type will still be the object encapsulated by Axios (AxiosResponse).

While working properly, Smart Tips will not work.

You can encapsulate a method that receives a generic type and call it internally request().

// src\utils\request.ts
import axios, {
    
     AxiosRequestConfig } from 'axios'

...

export default <T = any>(config: AxiosRequestConfig) => {
    
    
  return request(config).then(res => (res.data.data || res.data) as T)
}

But in this way, you can't use request.get()the method to send the request:

// 之前定义的 interface ResponseData 就不需要了
interface DemoData {
    
    
  title: string
  date: number
}

export const demo = () => {
    
    
  return request<DemoData>({
    
    
    method: 'GET',
    url: '/demo'
  })
}

This method cannot be used to request.<method>send requests, there are advantages and disadvantages, choose to use according to personal habits.

Extract interface type module

The format of the response data of the general interface may be used in multiple places. In order to reuse their interface types, it can be extracted into a module separately.

src/apiCreate a folder under the directory to typesstore API-related type modules:

// src\api\types\common.ts
export interface DemoData {
    
    
  title: string
  date: number
}

use:

// src\api\common.ts
// 公共基础接口封装
import request from '@/utils/request'
import {
    
     DemoData } from '@/api/types/common'

export const demo = () => {
    
    
  return request<DemoData>({
    
    
    method: 'GET',
    url: '/demo'
  })
}

<!-- src\views\login\index.vue -->
<template>
  <div>
    登录
  </div>
</template>

<script setup lang="ts">
import {
      
       demo } from '@/api/common'
import {
      
       DemoData } from '@/api/types/common'
import {
      
       onMounted, ref } from 'vue'

const data = ref<DemoData>()

onMounted(() => {
      
      
  demo().then(res => {
      
      
    data.value = res
    console.log(data.value.title)
  })
})

</script>

Environment variables and patterns

Environment Variables and Patterns | Vite Official Chinese Documentation

Generally, the base address (baseUrl) of different environments will be configured for the interface of the project, which is usually configured in the environment variable.

Vite exposes environment variables on a special import.meta.env.[variable]object. When building, these environment variables will be identified as strings and replaced statically, so dynamic key values ​​cannot be used, eg import.meta.env[variable].

Vite supports the same way as Vue CLI, .env.[mode]specifying environment variables through files.

Unlike Vue CLI, the latter's custom variables must start VUE_APP_with , while Vite must VITE_start with .

Configure environment variables

Values ​​are replaced as strings and can be unquoted unless included #.

# .env.development
# 开发模式下加载的环境变量
VITE_API_BASEURL=http://localhost:5000/api/admin

# .env.production
# 生产模式下加载的环境变量
VITE_API_BASEURL=http://localhost:5000/api/admin

// src\utils\request.ts
import axios, {
    
     AxiosRequestConfig } from 'axios'

// 创建 axios 实例
const request = axios.create({
    
    
  baseURL: import.meta.env.VITE_API_BASEURL
})

...

Note: Modifying environment variables requires restarting Vite ( npm run dev) to take effect.

Environment variables TypeScript support

Vite only provides TS type definitions for default environment variables ( MODE, BASE_URL, PROD, DEV), and user-defined environment variables need to manually add type definitions.

// src\env.d.ts
...

// Vite 环境变量
// eslint-disable-next-line no-unused-vars
interface ImportMetaEnv {
    
    
  readonly VITE_API_BASEURL: string
}

Since the interface is defined but not used, eslint will report an error, and this rule can be disabled for this line of code.

cross-domain issues

Usually, the front-end project and the back-end project are deployed separately, and the server interface has cross-domain restrictions. There are many ways to solve cross-domain. The most mainstream front-end solutions are two:

development environment Production Environment
Configure CORS on the server Configure CORS on the server
Configure the development server proxy, such as vite's server.proxy and Vue CLI'sdevServer.proxy Configure production server proxy, such as nginx

Generally, the back-end development is too lazy to configure CORS. The common solution for the front-end is to configure the server reverse proxy (proxy).

The principle is to build a transit server to forward requests to avoid cross-domain problems. The interface requests a local address, and the server running the front-end code forwards it to the target server.

Change the base path of the request to a local path (it is agreed that /apithe request path at the beginning is an interface request that needs to be forwarded):

# .env.development
# 开发模式下加载的环境变量
VITE_API_BASEURL=/api

Remember to restart for the environment variables to take effect.

Configured /apireverse proxy:

// vite.config.ts
...

export default defineConfig({
    
    
  ...
  server: {
    
    
    proxy: {
    
    
      '/api': {
    
    
        // 目标地址
        target: 'http://localhost:5000/api/admin',

        // 有的服务器会验证 origin
        // 默认接收到的是真实的 origin 即 http://localhost:3000
        // 设置 changeOrigin 为 true 后,代理服务器就会把 origin 修改为 target 的 origin(http://localhost:5000)
        // 一般建议加上此设置
        changeOrigin: true,

        // 路径重写
        // 路径即请求路径,如 /api/demo
        // 默认会这样拼接: <target.path><path> 如 http://localhost:5000/api/admin/api/demo
        // 重新后为 http://localhost:5000/api/admin/demo
        rewrite: path => path.replace(/^\/api/, '')
      }
    }
  }
})

PS: The server code used in the current case is configured with CORS by default, which can be deleted app.use(cors())to test cross-domain effects.

Guess you like

Origin blog.csdn.net/u012961419/article/details/124300061