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-components
and unplugin-auto-import
these 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:
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):
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.ts
to 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, ref
without manual work import
.
You can imports
configure the automatically imported API (preset or custom rules) through the configuration item, or resolvers
configure 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.ts
The 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.ts
the content will be automatically filled, and .eslintrc-auto-import.json
the 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.ts
ESLint 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 import
work.
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:
- Not all APIs , such as Vue Router's
createRouter
will not be imported. For specific APIs that can be automatically imported, refer to unplugin-auto-import/src/presets.eslintrc-auto-import.json
If you do not need to add configuration after generating the file, it is recommendedenabled: true
to set tofalse
, 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 ElMessage
such 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
import
import components (complete import also requires manualimport
) - Import style files manually (
element-plus/theme-chalk/xxx.css
)
For example commonly used ElMessage
components:
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 ElMessageBox
confirmation button in uses ElButton
the component, although the rendering is successful, but because it is not automatically imported through the plug-in, there is no style.
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.prototype
globally ( ) 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.globalProperties
the 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 , , ElMessage
etc. $message
) ElMessageBox
will 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.globalProperties
the purpose of is to replace Vue.prototype
the 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 setup
use 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/api
organized 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 res
is 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.
request
Response 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.data
the fields under .
But each interface will return status
, msg
and data
fields. In order to avoid repeated declarations, they can be encapsulated into an interface type (interface), and data
defined 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 data
fields 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/api
Create a folder under the directory to types
store 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 /api
the 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 /api
reverse 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.