vue3 + vite + ts

634 阅读6分钟

一、项目初始化

1.1 使用vite创建项目

# pnpm
pnpm create vite

# npm
npm init vite@latest

# yarn
yarn create vite


Need to install the following packages: 
    create-vite@latest 
Ok to proceed? (y) y 
√ Project name: ... vite-vue3-admin-ts 
√ Select a framework: » vue 
√ Select a variant: » vue-ts

1.2 代码规范和eslint

1.2.1 安装eslint

npm install eslint --save-dev

1.2.2 初始化eslint配置

//初始化命令
npx eslint --init
// 提示信息
You can also run this command directly using 'npm init @eslint/config'.
Need to install the following packages:
  @eslint/create-config
Ok to proceed? (y) y
√ How would you like to use ESLint? · style       
√ What type of modules does your project use? · esmWhich framework does your project use? · vue
√ Does your project use TypeScript? · No / YesWhere does your code run? · browser
√ How would you like to define a style for your project? · guide
√ Which style guide do you want to follow? · standard
√ What format do you want your config file to be in? · JavaScript
Checking peerDependencies of eslint-config-standard@latest
The config that you've selected requires the following dependencies:

eslint-plugin-vue@latest @typescript-eslint/eslint-plugin@latest eslint-config-standard@latest eslint@^8.0.1 eslint-plugin-import@^2.25.2 eslint-plugin-n@^15.0.0 eslint-plugin-promise@^6.0.0 @typescript-eslint/parser@latestWould you like to install them now? · No / YesWhich package manager do you want to use? · npm
Installing eslint-plugin-vue@latest, @typescript-eslint/eslint-plugin@latest, eslint-config-standard@latest, eslint@^8.0.1, eslint-plugin-import@^2.25.2, eslint-plugin-n@^15.0.0, eslint-plugin-promise@^6.0.0, @typescript-eslint/parser@latest

added 109 packages in 16s
Successfully created .eslintrc.js file in D:\vscode\gitee-pro\vite-vue3-admin-ts

1.2.3 在package.json文件中添加lint命令

  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview",
    "lint":"eslint ./src/**/*.{js,jsx,vue,ts,tsx} --fix"
  },

1.2.3 eslint配置文件

module.exports = {
  env: {
    browser: true,
    es2021: true
  },
  extends: [
     // 使用vue3规则
    'plugin:vue/vue3-strongly-recommended',
    'standard'
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser',
    sourceType: 'module'
  },
  plugins: [
    'vue',
    '@typescript-eslint'
  ],
  rules: {
  }
}

1.2.3 配置git commit hook

  1. 安装所需包
npx mrm@2 lint-staged
//提示信息
Need to install the following packages:
  mrm@2
Ok to proceed? (y) y
Running lint-staged...
Update package.json
Installing lint-staged and husky...
husky - Git hooks installed
husky - created .husky/pre-commit
  1. package.json文件中,配置git commit钩子函数
  "lint-staged": {
    "*.{js,jsx,vue,ts,tsx}":[
      "npm run lint",
      "git add"
    ]
  }

1.2.4 在开发和构建的时候进行验证

  1. 安装所需包
npm install vite-plugin-eslint --save-dev
  1. vite.config.ts中配置plugins
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import eslintPlugin from 'vite-plugin-eslint'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    eslintPlugin({
      exclude: ['./node_modules/**'],
      cache: false
    })
  ]

})

1.2.5 gitcommit规范

1.2.5.1 validate-commit-msg github

validate-commit-msg 用于检查 Node 项目的 Commit message 是否符合格式

  1. 安装所需包
# Install commitlint cli and conventional config(mac/linux)
npm install --save-dev @commitlint/{config-conventional,cli}
# For Windows:
npm install --save-dev @commitlint/config-conventional @commitlint/cli
  1. 根目录下创建commitlint.config配置文件
module.exports = {
  extends: [
    '@commitlint/config-conventional'
  ]
}

  1. To lint commits before they are created you can use Husky's commit-msg hook:

之前安装lint-staged时,已安装并激活Husky,步骤3可以省略

# Install Husky v6
npm install husky --save-dev
# or
yarn add husky --dev

# Activate hooks
npx husky install
# or
yarn husky install
  1. Add hook
// 创建配置文件
npx husky add .husky/commit-msg ''

配置commit-msg文件

#!/bin/sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no-install commitlint --edit "$1"

1.3 JSX支持

  1. 安装@vitejs/plugin-vue-jsx
npm install @vitejs/plugin-vue-jsx -D
  1. vite.config.js配置JSX
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import eslintPlugin from 'vite-plugin-eslint'
// 引入JSX支持
import vueJsx from '@vitejs/plugin-vue-jsx'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    eslintPlugin({
      exclude: ['./node_modules/**'],
      cache: false
    }),
    vueJsx()
  ]

})

1.4 初始化vue-router

  1. 安装vue-router
npm install vue-router@4
  1. src下新建views/home/index.vue
<template>
  <h1>首页</h1>
</template>

<script setup>
</script>

<style lang="scss" scoped>

</style>

  1. src下新建router/index.ts
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'home',
    component: () => import('../views/home/index.vue')
  },
  {
    path: '/login',
    name: 'login',
    component: () => import('../views/login/index.vue')
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router

  1. main.ts引入router
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

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

  1. 修改App.vue
<template>
  <router-view />
</template>

<script setup lang="ts">
</script>
  1. 遇到的问题

Eslint 报错 error Component name "index" should always be multi-word vue/multi-word-component-names

解决方案

  • 按照规则,使用驼峰命名,例如 AppHeader.vue
  • 在 .eslintrc.js 文件中关闭命名规则
module.exports = {
  env: {
    browser: true,
    es2021: true
  },
  extends: [
    'plugin:vue/vue3-strongly-recommended',
    'standard'
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser',
    sourceType: 'module'
  },
  plugins: [
    'vue',
    '@typescript-eslint'
  ],
  rules: {
    'vue/multi-word-component-names': [
      'error',
      {
        ignores: ['index'] // 需要忽略的组件名
      }
    ]

  }
}

1.5 初始化pinia

  1. 安装 pinia包.
npm install pinia
  1. src下新建store/index.ts
import { createPinia } from "pinia";

const pinia = createPinia()

export default pinia
  1. main.ts引入vuex
import { createApp } from 'vue'
import pinia from '@/store'

const app = createApp(App)
app.use(pinia)

1.6 路径别名

  1. vite.config.ts配置别名
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import eslintPlugin from 'vite-plugin-eslint'
// 引入JSX支持
import vueJsx from '@vitejs/plugin-vue-jsx'
import { resolve } from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    eslintPlugin({
      exclude: ['./node_modules/**'],
      cache: false
    }),
    vueJsx()
  ],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      comps: resolve(__dirname, 'src/components'),
      api: resolve(__dirname, 'src/api'),
      views: resolve(__dirname, 'src/views'),
      utils: resolve(__dirname, 'src/utils'),
      routes: resolve(__dirname, 'src/routes'),
      styles: resolve(__dirname, 'src/styles'),
      layouts: resolve(__dirname, 'src/layouts'),
      plugins: resolve(__dirname, 'src/plugins')
    }
  }

})

  1. 路径声明变量
{
  "compilerOptions": {
     ...
    "paths": {
      "@/*":[
        "./src/*"
      ]
    }
  },
}

1.7 自动导入

  1. 安装unplugin-auto-import
npm install unplugin-auto-import -D
  1. 配置vite.config.ts
...
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [AutoImport()],
  ...
});

1.8 Sass/Scss 预处理器

以前用vuecli的时候,还要安装sass-loader、node-sass什么的,但是vite其实安装sass就可以了。

  1. 安装sass依赖
npm install -D sass

2.配置全局的scss文件,在src下新建styles/index.scssstyles/common.scss styles/index.scss

@import './common.scss'; // 全局公共样式
  1. vite.config.ts里配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import eslintPlugin from 'vite-plugin-eslint'
// 引入JSX支持
import vueJsx from '@vitejs/plugin-vue-jsx'
import { resolve } from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    eslintPlugin({
      exclude: ['./node_modules/**'],
      cache: false
    }),
    vueJsx()
  ],
  ...
  css: {
    preprocessorOptions: {
      // 引入公用的样式
      scss: {
        additionalData: '@import "@/styles/index.scss";'
      }
    }
  }

})

1.9 封装axios

  1. 安装axios
npm install axios
  1. 封装axios(参照诸葛小愚实例)
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
import { ElMessage } from 'element-plus'
// 数据返回的接口
// 定义请求响应参数,不含data
interface Result {
  code: number;
  msg: string
}

// 请求响应参数,包含data
interface ResultData<T = any> extends Result {
  data?: T;
}
const URL: string = ''
enum RequestEnums {
  TIMEOUT = 20000,
  OVERDUE = 600, // 登录失效
  SUCCESS = 200, // 请求成功
}
const config = {
  // 默认地址
  baseURL: URL as string,
  // 设置超时时间
  timeout: RequestEnums.TIMEOUT as number,
  // 跨域时候允许携带凭证
  withCredentials: true
}

class RequestHttp {
  // 定义成员变量并指定类型
  service: AxiosInstance
  public constructor (config: AxiosRequestConfig) {
    // 实例化axios
    this.service = axios.create(config)

    /**
     * 请求拦截器
     * 客户端发送请求 -> [请求拦截器] -> 服务器
     * token校验(JWT) : 接受服务器返回的token,存储到vuex/pinia/本地储存当中
    */
    this.service.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        const token = localStorage.getItem('token') || ''
        return {
          ...config,
          headers: {
            'x-access-token': token // 请求头中携带token信息
          }
        }
      },
      (error: AxiosError) => {
        // 请求报错
        Promise.reject(error)
      }
    )

    /**
     * 响应拦截器
     * 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
    */
    this.service.interceptors.response.use(
      (response: AxiosResponse) => {
        const { data } = response // 解构
        if (data.code === RequestEnums.OVERDUE) {
          // 登录信息失效,应跳转到登录页面,并清空本地的token
          localStorage.setItem('token', '')
          // router.replace({
          //   path: '/login'
          // })
          return Promise.reject(data)
        }
        // 全局错误信息拦截(防止下载文件得时候返回数据流,没有code,直接报错)
        if (data.code && data.code !== RequestEnums.SUCCESS) {
          ElMessage.error(data) // 此处也可以使用组件提示报错信息
          return Promise.reject(data)
        }
        return data
      },
      (error: AxiosError) => {
        const { response } = error
        if (response) {
          this.handleCode(response.status)
        }
        if (!window.navigator.onLine) {
          ElMessage.error('网络连接失败')
          // 可以跳转到错误页面,也可以不做操作
          // return router.replace({
          //   path: '/404'
          // })
        }
      }
    )
  }

  handleCode (code: number):void {
    switch (code) {
      case 401:
        ElMessage.error('登录失败,请重新登录')
        break
      default:
        ElMessage.error('请求失败')
        break
    }
  }

  // 常用方法封装
  get<T> (url: string, params?: object): Promise<ResultData<T>> {
    return this.service.get(url, { params })
  }

  post<T> (url: string, params?: object): Promise<ResultData<T>> {
    return this.service.post(url, params)
  }

  put<T> (url: string, params?: object): Promise<ResultData<T>> {
    return this.service.put(url, params)
  }

  delete<T> (url: string, params?: object): Promise<ResultData<T>> {
    return this.service.delete(url, { params })
  }

  downFile<T> (url: string, params?: object): Promise<ResultData<T>> {
    return this.service.get(url, { params, responseType: 'blob' })
  }
}

// 导出一个实例对象
export default new RequestHttp(config)

  1. 调用封装axios src/api/user.ts
import axios from '@/utils/request'
namespace Login {
  // 用户登录表单
  export interface LoginReqForm {
    username: string;
    password: string;
  }
  // 登录成功后返回的token
  export interface LoginResData {
    token: string;
  }
}
// 用户登录
export const login = (params: Login.LoginReqForm) => {
  // 返回的数据格式可以和服务端约定
  return axios.post<Login.LoginResData>('/user/login', params)
}

  1. import.meta.globEager自动加载接口函数 src/api/api.ts
const requireComponent = import.meta.globEager('/src/api/**/*.ts')
const modules: any = {}
Object.entries(requireComponent).forEach(([k, v]) => {
  const pattern = /\/(\w*)\.ts/
  const matched = k.match(pattern)
  if (matched && matched[1] !== 'api') {
    Object.assign(modules, v)
  }
})
export default modules
  1. 页面调用
import api from '@/api/api'
console.dir(api.login({ admin: 'admin',password:'123456' }))

2.0 elementPlus按需加载

  1. 安装unplugin-vue-components 和 unplugin-auto-import
npm install -D unplugin-vue-components unplugin-auto-import
  1. 配置vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})
  1. 国际化 App.vue

<template>
  <el-config-provider
    :locale="zhCn"
    :z-index="zIndex"
    size="small"
  >
    <router-view />
  </el-config-provider>
</template>
// :size="size"
<script setup lang="ts">
import { ElConfigProvider } from 'element-plus'
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
const zIndex = 2000

</script>