Vue3+Vite+Ts project combat 03 Initialize Element Plus, API e importação automática de componentes, uso do componente ElMessage, propriedades globais do Vue, Axios, variáveis de ambiente, cross-domain

Inicializar elemento mais

Element Plus é uma versão atualizada do Element UI para Vue 3.

Instalar e configurar a importação automática sob demanda

# 安装
npm install element-plus --save

Recomenda-se o uso de importação sob demanda A recomendação oficial é usar unplugin-vue-componentsesses unplugin-auto-importdois plug-ins para implementar a importação automática para compensar algumas deficiências da importação sob demanda (registro manual de componentes, etc.).

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

Configurar 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()]
    })
  ],
  ...
})

teste:

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

globalização

Os componentes do Element Plus usam o inglês por padrão, como o componente de data:

insira a descrição da imagem aqui

Se você quiser usar outros idiomas (como o chinês), precisará configurar a internacionalização globalmente.

O método de importação completa pode ser configurado através de opções durante o registro locale.

A importação sob demanda precisa ser configurada usando os componentes Vue fornecidos oficialmente:

<!-- 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>

Efeito (você precisa atualizar a página para que a configuração entre em vigor):

insira a descrição da imagem aqui

ícone

Se você quiser usar o ícone Element Plus diretamente no projeto como o caso oficial, você precisa registrar o componente globalmente. O plug-in oficial importado automaticamente ainda está em desenvolvimento e o registro global do manual atual:

// 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 e importação automática de componentes

Os plug-ins para importação e instalação automática do Element Plus não são usados ​​apenas para si, na verdade, esses dois plug-ins podem ser aplicados a vários frameworks e bibliotecas.

unplugin-vue-components

O plug-in unplugin-vue-components é usado para identificar automaticamente os componentes usados ​​no modelo Vue, importar e registrar automaticamente sob demanda.

Ele fornece analisadores para várias bibliotecas de UI configuráveis, consulte a seção "Importando de bibliotecas de UI".

Um arquivo será gerado no projeto TypeScript components.d.tspara complementar e atualizar automaticamente o arquivo de declaração de tipo do componente.

**Observação: **O plug-in reconhece automaticamente os componentes usados ​​no modelo modelocomponents.d.ts , você pode verificar para confirmar se foi reconhecido.

unplugin-auto-import

O plugin unplugin-auto-import pode importar automaticamente APIs comuns de bibliotecas de configuração sob demanda em ambientes Vite, Webpack, Rollup e esbuild, como o Vue, refsem trabalho manual import.

Você pode importsconfigurar a API importada automaticamente (regras predefinidas ou personalizadas) por meio do item de configuração ou resolversconfigurar o analisador da biblioteca de componentes (como Element Plus).

Em um projeto que suporta TypeScript, um arquivo será gerado no diretório raiz do projeto após a instalação do plug-in auto-imports.d.tsQuando a configuração for importada automaticamente, a declaração de tipo correspondente à API da biblioteca de configuração será complementada automaticamente.

Nota: auto-imports.d.tsO arquivo será verificado pelo ESLint por padrão, e um erro será relatado <变量> is defined but never used.. Você pode ignorar a verificação do arquivo pelo ESLint.

Configurar a importação automática da API

Configure a importação automática da API para Vue, Vue Router e 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()]
    }),
    ...
  ],
  ...
})

Após o salvamento entrar em vigor, auto-imports.d.tso conteúdo será preenchido automaticamente e .eslintrc-auto-import.jsona configuração da variável global eslint será gerada no diretório raiz do projeto.

Esses dois arquivos precisam ser adicionados manualmente aos arquivos de configuração TypeScript e ESLint:

// 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'
  ],
  ...
}

Ignora auto-imports.d.tsa validação ESLint.

# .eslintignore
auto-imports.d.ts

Recomenda-se reiniciar o editor para que a configuração tenha efeito.

Agora você pode usar diretamente a API do Vue, Vue Router e Pinia sem importtrabalho manual.

Por exemplo, você pode anotar as importações das seguintes 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'

...

Perceber:

  1. Nem todas as APIs , como Vue Router, createRouternão serão importadas. Para APIs específicas que podem ser importadas automaticamente, consulte unplugin-auto-import/src/presets
  2. .eslintrc-auto-import.jsonSe você não precisar adicionar configuração após gerar o arquivo, é recomendável enabled: truedefinir como false, caso contrário, esse arquivo será gerado todas as vezes.

Referência separada para componentes ElementPlus

O princípio da importação automática sob demanda é <template>importar automaticamente identificando os componentes usados ​​no , o que leva ao fato de que, se você usar ElMessageesses componentes que chamam métodos diretamente no JS, o plug-in não reconhecerá e concluirá a importação automática.

Por exemplo:

  • <script>Usado em componentes de arquivo único Vue
  • Usado no interceptador Axios

Portanto, esses componentes precisam ser operados manualmente:

  • Importar componentes manualmente import(a importação completa também requer manual import)
  • Importar arquivos de estilo manualmente ( element-plus/theme-chalk/xxx.css)

ElMessagePor exemplo , componentes comumente usados :

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

ElMessage.success('成功使用')

Mas ainda há um problema com isso, pois alguns componentes também utilizam outros componentes Element, como o ElMessageBoxbotão de confirmação em usa ElButtono componente, embora a renderização seja bem-sucedida, mas por não ser importado automaticamente pelo plug-in, não há estilo.

insira a descrição da imagem aqui

Se for usado em um componente de arquivo único Vue e usado no modelo <el-button>, ele acionará a importação automática do arquivo de estilo do componente.

Portanto, é recomendável importar sob demanda e ainda importar arquivos de estilo completos para evitar esses problemas de limite.

// 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('成功使用')

Propriedades globais do Vue (globalProperties)

Documentação oficial:

No Vue 2, variáveis ​​e métodos que podem ser acessados Vue.prototype​​globalmente ( ) para todas as instâncias do Vue podem ser definidos de uma só vez .this

O Vue 3 usa o objeto na instância raiz para registrar as propriedades globais acessadas por todas as instâncias do componente na app.config.globalPropertiesinstância ( ), em vez do método do Vue 2 de modificar todas as instâncias raiz.app

Registre o componente de prompt de mensagem ElementPlus globalmente

Se o Element Plus for totalmente introduzidoapp.config.globalProperties , alguns métodos globais de componentes (como , , ElMessageetc. $message) ElMessageBoxserão adicionados automaticamente.$msgbox$alert

No entanto, se você usar o método de importação sob demanda , ao usar esse tipo de componente, será necessário importar manualmente o componente e o estilo do componente no módulo usado.

Para reduzir importações repetidas em componentes Vue, eles podem ser registrados como variáveis ​​globais da instância Vue (o nome da variável refere-se ao nome do registro de importação completo):

// 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
  }
}

usar variáveis ​​globais

Pode ser acessado por meio de variáveis ​​globais na API de opções this.<globalProperty>ou usado diretamente nos modelos:

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

Use variáveis ​​globais na configuração

O funcionário não apresenta como usar variáveis ​​globais em setup()e .<script setup>

Como app.config.globalPropertieso objetivo é substituir Vue.prototypeo uso do 2.x pela atualização da API global, não haverá mais uma configuração global única do Vue, mas uma configuração separada para cada instância raiz .

Isso não deve ser usado em setup, você deve importar o conteúdo diretamente ou setupusá-lo em provide/inject.

Consulte Problemas:

modo fornecer/injetar

<!-- 父级组件,如 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>

Como obter a instância na configuração (não recomendado)

De outra forma, o Vue expõe uma API que getCurrentInstance()pode acessar instâncias de componentes internos, por meio das quais as variáveis ​​globais da instância raiz podem ser acessadas:

<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>

sugestão

Existem várias formas de utilizá-lo, sendo recomendável utilizar uma forma segura, como a importação direta ou o uso sob a opção API.

Declaração de tipo TypeScript de variável global

Adicionar arquivo de declaração de tipo:

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

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

Módulo de solicitação de pacotes baseado no Axios

npm i axios

Configuração básica

// 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

Todas as solicitações de interface são src/apiorganizadas no diretório:

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

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

usar:

<!-- 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>

O tipo de interface que encapsula os dados de resposta

O que é obtido atualmente resé o objeto de resposta empacotado pelo Axios.Os dados reais retornados pelo back-end não possuem um tipo declarado, portanto o IDE não pode fornecer prompts inteligentes, portanto, o tipo dos dados de resposta deve ser declarado manualmente.

requestDados de resposta genéricos não são suportados, portanto, enviar solicitações que suportem genéricos devem ser usados request.<method>​​.

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

Agora o IDE pode solicitar automaticamente res.dataos campos em .

Mas cada interface retornará status, msge datacampos. Para evitar declarações repetidas, eles podem ser encapsulados em um tipo de interface (interface) e datadefinidos como um tipo genérico:

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

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

Encapsular métodos de solicitação genéricos

Agora o acesso aos datacampos de dados de resposta precisa ser passado res.data.data.title.

Se quiser ser mais conciso, por exemplo res.title, pode retornar após a requisição .then(res => res.data.data).

Portanto, esta ação deve ser adicionada após cada solicitação.

Normalmente processamos no interceptor axios, mas request.get()o tipo de retorno ainda será o objeto encapsulado pelo Axios (AxiosResponse).

Enquanto estiver funcionando corretamente, o Smart Tips não funcionará.

Você pode encapsular um método que recebe um tipo genérico e chamá-lo internamente 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)
}

Mas desta forma, não dá para usar request.get()o método para enviar a requisição:

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

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

Este método não pode ser usado para request.<method>enviar solicitações, existem vantagens e desvantagens, opte por usar de acordo com os hábitos pessoais.

Extrair módulo de tipo de interface

O formato dos dados de resposta da interface geral pode ser usado em vários lugares, para reutilizar seus tipos de interface, eles podem ser extraídos em um módulo separadamente.

src/apiCrie uma pasta no diretório para typesarmazenar módulos de tipo relacionados à API:

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

usar:

// 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>

Variáveis ​​e padrões de ambiente

Variáveis ​​e padrões de ambiente | Vite Documentação oficial chinesa

Geralmente, o endereço base (baseUrl) de diferentes ambientes será configurado para a interface do projeto, que normalmente é configurada na variável de ambiente.

O Vite expõe variáveis ​​de ambiente em um import.meta.env.[variable]objeto especial. Ao compilar, essas variáveis ​​de ambiente serão identificadas como strings e substituídas estaticamente, portanto, valores de chave dinâmicos não podem ser usados, por exemplo, import.meta.env[variable].

O Vite oferece suporte da mesma forma que o Vue CLI, .env.[mode]especificando variáveis ​​de ambiente por meio de arquivos.

Ao contrário do Vue CLI, as variáveis ​​personalizadas deste último devem começar VUE_APP_com , enquanto o Vite deve VITE_começar com .

Configurar variáveis ​​de ambiente

Os valores são substituídos como strings e não podem ser citados, a menos que sejam incluídos #.

# .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
})

...

Nota: Modificar as variáveis ​​de ambiente requer reiniciar o Vite ( npm run dev) para entrar em vigor.

Variáveis ​​de ambiente Suporte a TypeScript

O Vite fornece apenas definições de tipo TS para variáveis ​​de ambiente padrão ( MODE, BASE_URL, PROD, DEV) e as variáveis ​​de ambiente definidas pelo usuário precisam adicionar definições de tipo manualmente.

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

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

Como a interface está definida, mas não é usada, o eslint relatará um erro e essa regra poderá ser desativada para esta linha de código.

problemas entre domínios

Normalmente, o projeto de front-end e o projeto de back-end são implantados separadamente, e a interface do servidor possui restrições entre domínios. Há muitas maneiras de resolver o problema entre domínios. As soluções de front-end mais comuns são duas:

ambiente de desenvolvimento Ambiente de produção
Configurar o CORS no servidor Configurar o CORS no servidor
Configure o proxy do servidor de desenvolvimento, como server.proxy do vite e CLI do VuedevServer.proxy Configure o proxy do servidor de produção, como nginx

Geralmente, o desenvolvimento back-end é muito preguiçoso para configurar o CORS. A solução comum para o front-end é configurar o proxy reverso do servidor (proxy).

O princípio é construir um servidor de trânsito para encaminhar solicitações para evitar problemas entre domínios.A interface solicita um endereço local e o servidor que executa o código front-end o encaminha para o servidor de destino.

Altere o caminho base da requisição para um caminho local (concorda-se que /apio caminho da requisição no início é uma requisição de interface que precisa ser encaminhada):

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

Lembre-se de reiniciar para que as variáveis ​​de ambiente entrem em vigor.

/apiProxy reverso configurado :

// 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: O código do servidor usado no caso atual é configurado com CORS por padrão, que pode ser excluído app.use(cors())para testar os efeitos entre domínios.

Acho que você gosta

Origin blog.csdn.net/u012961419/article/details/124300061
Recomendado
Clasificación