Vue3 项目搭建

TIP

vite + ts + pnpm + vue3 + vue router + pinia + elment-plus + sass + axios

node 版本 16+

编辑器:VSCode , 插件如下:

  • Vue Language Features (Volar)
  • TypeScript Vue Plugin (Volar)
  • Prettier - Code formatter
  • ESLint

PS:禁用 Vetur 和 Vue VSCode Snippets > vue2 的两个插件

二、pnpm 包管理

1. 官网

中文文档open in new window

2. 常用命令

# 安装
npm install -g pnpm

# 升级
pnpm add -g pnpm

# 初始化package.json
pnpm init -y

# 初始化包
pnpm install

三、Vite 创建项目

1. 创建项目

pnpm create vite demo -- --template vue-ts
cd demo
pnpm install
pnpm run dev

2. 根目录新建 .npmrc

shamefully-hoist = true

四、路径别名配置

1. 使用 @ 代替 src

// 根目录:vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  // 新加:相对路径别名配置,使用 @ 代替 src
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
})

2. node 模块的 ts 报错

import path from 'path'编译器报错:TS2307: Cannot find module 'path' or its corresponding type declarations. 本地安装 Node 的 TypeScript 类型描述文件即可解决编译器报错

# 安装@types/node
pnpm install @types/node --D

import path from 'path' 编译报错: TS1259: Module '"path"' can only be default-imported using the 'allowSyntheticDefaultImports' flag 因为 typescript 特殊的 import 方式 , 需要配置允许默认导入的方式,还有路径别名的配置

// 根目录:tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
    "paths": { //路径映射,相对于baseUrl
      "@/*": ["src/*"]
    },
    "allowSyntheticDefaultImports": true // 允许默认导入
  }
}

4. ts 识别.vue 文件

// 根目录:vite-env.d.ts

/// <reference types="vite/client" />

declare module '*.vue' {
  import { DefineComponent } from 'vue'
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  const component: DefineComponent<{}, {}, any>
  export default component
}

5. 别名使用

// 根目录:App.vue
import HelloWorld from '/src/components/HelloWorld.vue'import HelloWorld from '@/components/HelloWorld.vue'

6. 运行项目

pnpm install

pnpm run dev

五、样式-Sass

1. 官网

中文官网open in new window

2. 安装

pnpm install sass -D

3. 使用配置

// vite.config.ts

// 可以引入多个自定义的sass文件,比如变量,element ui的覆盖文件等
css: {
  preprocessorOptions: {
    scss: {
      additionalData: `@use "@/assets/scss/config.scss" as *;
      @use "@/assets/scss/override.scss" as *;`,
    },
  },
},

4. 具体代码文件修改 scss

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

六、element-plus 组件库

1. 官网

中文官网open in new window

2. 安装

pnpm install element-plus

3. 使用配置

(1) 全局引入组件库

注意 默认为英语,需要引入中文进行修改-参照国际化文档即可

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
// 引入element-plus
import ElementPlus from 'element-plus'
import 'element-plus/theme-chalk/src/index.scss'

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

(2) 全局组件 TS 声明

// tsconfig.json
{
  "compilerOptions": {
    // ...
    "types": ["element-plus/global"]
  }
}

(3) 页面中使用

// src/App.vue
<template>
    <el-button :icon="Search" circle></el-button>
  </div>
</template>

<script lang="ts" setup>
     import {Search} from '@element-plus/icons-vue'
</script>

4. 按需引入 + 全局 API 导入

PS:问题较多,不建议使用

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

# 2 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: [
    ...
    // 按需导入
    AutoImport({
      resolvers: [ElementPlusResolver({ importStyle: 'sass' })],
    }),
    Components({
      resolvers: [ElementPlusResolver({ importStyle: 'sass' })]
    })
  ]
})

# 3 main.ts去除全局样式

5. 自动导入全局 api

# 自动导入 vue vuex vue-router 等 api
pnpm install unplugin-auto-import -D


# vite.config.ts  文件

plugins: [
  AutoImport({
    vue(),
    // 自动导入全局方法
    imports: ['vue', 'pinia', 'vue-router'],
    dts: 'src/auto-import.d.ts'
    // 全局引入eslint报错
    // eslintrc: {
    //   enabled: false, // 开启true自动生成了,就关闭,每次imports加入新的再打开
    //   filepath: './.eslintrc-auto-import.json',
    //   globalsPropValue: true
    // }
  }),
]

# .eslintrc.cjs 引入自动生成的文件
extends: [
  ...
  // 自动引入全局方法
  './.eslintrc-auto-import.json'
],

七、引入图标库

1. 官方文档

中文文档open in new window

2. 安装

pnpm install @element-plus/icons-vue -S

3. 使用配置

(1) 按需引入

// 1 引入图标库
import { Delete, Edit } from '@element-plus/icons-vue'

// 2 放入components
components: {
  Delete,
  Edit,
}

// 3 在页面中使用
<el-icon>
  <Delete />
</el-icon>

(2) 全局引入

// 1 在main.ts中引入图标库
import * as ElIcons from '@element-plus/icons-vue'

// 2 全部注册为全局组件
for (const name in ElIcons) {
  app.component(name, (ElIcons as any)[name])
}

// 3 在页面中使用
<!-- 结合按钮使用 -->
<el-button type="primary" icon="Edit">编辑</el-button>
<!-- 结合el-icon使用 -->
<el-icon>
  <Delete />
</el-icon>

八、安装路由

1. 官网

中文官网open in new window

2. 安装

pnpm install vue-router

3. 使用配置

(1) 根目录新建 router/index.ts

(2) 配置路由

import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/layout/Layout.vue'

const routes = [
  {
    path: '/',
    component: Layout,
    redirect: '/home',
    children: [
      {
        path: 'home',
        name: 'home',
        component: () => import('@/pages/home/Home.vue'),
        meta: {
          title: '首页',
          // 固定不能被删除
          affix: true,
        },
      },
    ],
  },
  {
    path: '/system',
    component: Layout,
    redirect: '/system/user',
    meta: {
      title: '系统管理',
      // 就算只有一个子节点也现实父节点
      alwaysShow: true,
    },
    children: [
      {
        path: 'user',
        name: 'User',
        component: () => import('@/pages/system/user/User.vue'),
        meta: {
          title: '用户管理',
          // 设置tagsView缓存,false或者不写的时候会缓存
          noCache: false,
        },
      },
    ],
  },
]

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

// 路由拦截
router.beforeEach((to, from, next) => {
  next()
})

router.afterEach(() => {
  window.scrollTo(0, 0)
})

export default router

(3) main.ts 中注入

import router from './router'
app.use(router)

九、状态管理-pinia

1. 官网

2. 安装

pnpm install pinia

3. 全局注册

// src/main.ts
import { createPinia } from 'pinia'
app.use(createPinia()).mount('#app')

4. 模块封装

// src/store/modules/user.ts
// 用户状态模块
import { defineStore } from 'pinia'
import { reactive, toRefs } from 'vue'

// 类似vuex中的老的写法 - 不推荐
export const useUserStore = defineStore('user', {
  // other options...
  state: () => ({
    count: 0,
  }),
  getters: {
    count1: (state) => state.count,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

// 响应式新写法 - 推荐
export const useUserStore = defineStore('user', () => {
  // setup入口函数
  const state = reactive({ count: 0 })
  const increment = () => {
    state.count++
  }
  return {
    ...toRefs(state),
    increment,
  }
})

不推荐用 index.ts 导出全部,不直观

5. 使用

// src/App.vue
<script setup lang="ts">
import { useUserStore } from '@/stores/user'
const store = useUserStore()
</script>

<template>
 <p>Double count is {{ store.count1 }}</p>
  <el-button @click="store.increment()">add</el-button>
</template>

十、SVG 自定义图标使用

1. 安装

pnpm install vite-plugin-svg-icons -D

2. 配置

//  vite.config.ts

import { createSvgIconsPlugin } from "vite-plugin-svg-icons";

plugins: [
  vue(),
  ...
  createSvgIconsPlugin({
    // 图标存放目录
    iconDirs: [path.resolve(process.cwd(), "src/assets/images/icons")],
    // 图标位移标识-名称
    symbolId: "[name]",
    // 图标注入方式,放到body底部
    inject: "body-last",
    // 图标引入SVG的唯一标识-可自定义
    customDomId: `demo__svg__icons__dom__`,
  }),
],

3. 定义全局组件

// src\components\SvgIcon.vue
<template>
  <svg class="svg-icon" :class="className" aria-hidden="true">
    <use :href="`#${name}`"></use>
  </svg>
</template>
<script lang="ts" setup>
defineProps({
  // 图标名称
  name: {
    type: String,
    required: true
  },
  // 图标类名
  className: {
    type: String,
    default: ''
  }
})
</script>
<style scoped lang="scss">
.svg-icon {
  width: 1em;
  height: 1em;
  fill: currentColor;
  vertical-align: middle;
  overflow: hidden;
}
</style>

4. 全局注册

// src/main.ts
import SvgIcon from '@/components/SvgIcon.vue'
import 'virtual:svg-icons-register'

app.component('svg-icon', SvgIcon)

5. 使用组件

<svg-icon name="smc-setting" className="icon-arrow" />

十一、网络请求-axios

1. 安装

pnpm install axios -S

2. 自行封装

十二、代码规范

1. eslint

(1) 安装

基础配置

pnpm install eslint eslint-plugin-vue -D
  • eslint: ESLint 的核心代码。
  • eslint-plugin-vue:包含常用的 vue 规范。

配置 ts 支持

# ts支持
pnpm install  @typescript-eslint/eslint-plugin @typescript-eslint/parser -D
  • @typescript-eslint/parser:ESLint 的解析器,用于解析 typescript,从而检查和规范 Typescript 代码。
  • @typescript-eslint/eslint-plugin:包含了各类定义好的检测 Typescript 代码的规范。

(2) eslint 初始化

pnpm eslint --init
(1) How would you like to use ESLint?
选择:To check syntax and find problems

(2) What type of modules does your project use?
选择:JavaScript modules (import/export)

(3) Which framework does your project use?
选择:Vue.js

(4) Does your project use TypeScript?
选择:Yes

(5) Where does your code run?
选择:Browser
选择:Node

(6) What format do you want your config file to be in?
选择:JavaScript

(7) Would you like to install them now?
选择:Yes

(8) Which package manager do you want to use?
选择:pnpm

依赖安装完成后,会生成.eslintrc.cjs 配置文

(3) 配置.eslintrc.cjs

module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: ['eslint:recommended', 'plugin:vue/vue3-essential', 'plugin:@typescript-eslint/recommended'],
  overrides: [],
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
  },
  plugins: ['vue', '@typescript-eslint'],
  rules: {
    // 关闭组件多单词组件名称
    'vue/multi-word-component-names': 0,
    // https://eslint.bootcss.com/docs/rules/no-case-declarations
    'no-case-declarations': 0,
  },
}

(4) 配置执行命令

// package.json
{
    "scripts": {
        // eslint . 为指定lint当前项目中的文件
        // --ext 为指定lint哪些后缀的文件
        // --fix 开启自动修复
        "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix"
    }
}

(5) .eslintignore 忽略

*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
/bin
.editorconfig
.eslintrc.cjs
.prettierrc
.prettierignore
src/assets

# logs
.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

.DS_Store
dist-ssr
*.local

components.d.ts

2. prettier

(1) 安装

pnpm install prettier -D

(2) 根目录创建.prettierrc

{
  // 是否使用tab进行缩进,默认为false,表示用空格进行缩减
  "useTabs": false,
  // / 一个tab代表几个空格数
  "tabWidth": 2,
  // 一行的字符数,如果超过会进行换行,默认为80
  "printWidth": 120,
  // 字符串是否使用单引号,默认为false,使用双引号
  "singleQuote": true,
  // 是否使用尾逗号,有三个可选值"<none|es5|all>"
  "trailingComma": "none",
  // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
  "bracketSpacing": true,
  // 换行符使用 lf
  "endOfLine": "lf",
  // 行位是否使用分号,默认为true
  "semi": false,
  // 箭头函数,只有一个参数的时候,也需要括号
  "arrowParens": "always"
}

(3) .prettierignore 忽略

# 根目录创建.prettierignore

/dist/*
 .local
 .output.js
 /node_modules/**
 ​
 **/*.svg
 **/*.sh
 ​
 src/**/*.scss
 src/**/*.css

(4) 配置执行命令

PS: 非必须

// package.json
{
  "scripts": {
    # 代码格式化
    "prettier": "prettier --write  src/**/*.{js,json,tsx,css,less,scss,vue,html,md}",
  }
}

3. 解决 eslint 与 prettier 的冲突

pnpm install eslint-plugin-prettier eslint-config-prettier -D
// 在 .eslintrc.cjs 中 extends 的最后添加一个配置
// .eslintrc.cjs
{
    extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    'plugin:@typescript-eslint/recommended',
+    // 新增,必须放在最后面
+    'plugin:prettier/recommended'
  ],
}

4. vscode 插件安装

  • Vue Language Features (Volar)
  • TypeScript Vue Plugin (Volar)
  • Prettier - Code formatter
  • ESLint
  • EditorConfig for VS Code - 非必须

5. .editorconfig - 非必须

// 根目录 .editorconfig

# Editor configuration, see http://editorconfig.org

# 表示是最顶层的 EditorConfig 配置文件
root = true

[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行

[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false

十三、husky - 非必须

  • husky 是一个用来管理 git hook 的工具,在一些 git 操作前自动触发的函数
  • husky 官网open in new window
  • lint-staged 过滤出 git 代码暂存区文件,将所有暂存文件的列表传递给任务
  • commitlint 对 git commit 提交的注释进行校验

** pre-commit 配置**

# 1 安装
pnpm install husky lint-staged -D

# 2 package.json 添加脚本
{
  "scripts": {
    "prepare": "husky install"
  }
}

# 3 执行命令,生成.husky文件
pnpm run prepare

# 4 使用husky命令添加pre-commit钩子
pnpm husky add .husky/pre-commit "pnpm lint-staged"

# 5 package.json配置lint-staged脚本
"lint-staged": {
  "*.{js,vue,ts,jsx,tsx}": [
    "pnpm run lint"
  ]
}

# 6 测试一下
git add 文件
git commit -m "aa"
这里会在commit之前执行eslint的钩子函数,进行代码校验

** commit-msg 配置**

# 1 安装
pnpm install @commitlint/cli @commitlint/config-conventional -D

# 2 使用husky命令添加commit-msg钩子
# npm环境,完整命令
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit ${1}'

# pnpm环境完整命令
pnpm husky add .husky/commit-msg 'pnpm exec commitlint --config commitlint.config.cjs --edit "${1}"'

# 如果执行不成功就分开两个
pnpm husky add .husky/commit-msg
在commit-msg 文件加入
pnpm exec commitlint --config commitlint.config.cjs --edit "${1}"

# 3 测试一下
git add 文件

git commit -m "aa"  > 失败
git commit -m "feat: 提交新功能"  > 成果
// commitlint.config.cjs

// https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat', // 新功能(feature)
        'bug', // 此项特别针对bug号,用于向测试反馈bug列表的bug修改情况
        'fix', // 修补bug
        'ui', // 更新 ui
        'docs', // 文档(documentation)
        'style', // 格式(不影响代码运行的变动)
        'perf', // 性能优化
        'release', // 发布
        'deploy', // 部署
        'refactor', // 重构(即不是新增功能,也不是修改bug的代码变动)
        'test', // 增加测试
        'chore', // 构建过程或辅助工具的变动,比如增加依赖库
        'revert', // feat(pencil): add ‘graphiteWidth’ option (撤销之前的commit)
        'merge', // 合并分支, 例如: merge(前端页面): feature-xxxx修改线程地址
        'build', // 打包
      ],
    ],
    // <type> 格式 小写
    'type-case': [2, 'always', 'lower-case'],
    // <type> 不能为空
    'type-empty': [2, 'never'],
    // <scope> 范围不能为空
    'scope-empty': [0],
    // <scope> 范围格式
    'scope-case': [0],
    // <subject> 主要 message 不能为空
    'subject-empty': [2, 'never'],
    // <subject> 以什么为结束标志,禁用
    'subject-full-stop': [0, 'never'],
    // <subject> 格式,禁用
    'subject-case': [0, 'never'],
    // <body> 以空行开头
    'body-leading-blank': [1, 'always'],
    'header-max-length': [0, 'always', 72],
  },
}

常用 git hooks

  • pre-commit:由 git commit 调用,再 commit 之前执行
  • commit-msg:由 git commit 调用 或 git merge 调用 或者 --amend xxx
  • pre-merge-commit:由 git merge 调用,在 merge 之前执行
  • git push:被 git push 调用,在 git push 前执行,防止进行推送

十四、stylelint - 非必须

// 1 vscode 安装插件 styleLint插件

// 2 修改settings.json
{
  "editor.codeActionsOnSave": {
    "source.fixAll.stylelint": true
  },
  "stylelint.validate": ["css", "less", "vue", "scss"]
}

// 3 安装项目需要的校验库
pnpm install stylelint stylelint-config-standard -D

// 4 根目录建立.stylelintrc.cjs
module.exports = {
  extends: [
    'stylelint-config-standard',
  ],
}

// 5 执行命令校验css
npx stylelint "**/*.css"

// 6 增加其他文件的支持
// less
pnpm install stylelint-less stylelint-config-recommended-less -D

// sass
pnpm install stylelint-scss stylelint-config-recommended-scss postcss -D

// vue  vite已经提供了.scss .sass .less .styl .stylus文件的内置支持,不需要额外安装特定插件和预处理器
pnpm install postcss postcss-html stylelint-config-standard-scss stylelint-config-recommended-vue -D

// 7 sass的extends
extends: [
  'stylelint-config-standard-scss',
  'stylelint-config-recommended-vue/scss'
],

// 8 package.json添加命令
"lint:style": "stylelint **/*.{html,vue,css,sass,scss,less} --fix"

// 8 vite添加插件
pnpm install vite-plugin-stylelint -D

// vite.config.ts
import vitePluginStyleLint from 'vite-plugin-stylelint'
plugins: [..., vitePluginStyleLint({fix: true})]

// 9 添加到lint-stage里面,在暂存区对文件进行样式格式化
"lint-staged": {
  "*.{js,vue,ts,jsx,tsx}": [
    "pnpm run lint"
  ],
  "*.{vue,less,css,sass}": [
    "pnpm run lint:style"
  ],
},

// 10 添加忽略
// (1) 添加一个.stylelintignore文件,忽略不检查的css less scss等
/dist/*
/public/*

// (2) .stylelintrc.js配置忽略文件
ignoreFiles: [
  '**/*.js',
  '**/*.jsx',
  '**/*.tsx',
  '**/*.ts',
  '**/*.json',
  '**/*.md',
  '**/*.yaml'
],
// 11 .stylelintrc.cjs 其他配置

module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-prettier',
    'stylelint-config-recommended-less',
    'stylelint-config-standard-vue',
  ],
  plugins: ['stylelint-order'],
  // 不同格式的文件指定自定义语法
  overrides: [
    {
      files: ['**/*.(less|css|vue|html)'],
      customSyntax: 'postcss-less',
    },
    {
      files: ['**/*.(html|vue)'],
      customSyntax: 'postcss-html',
    },
  ],
  ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts', '**/*.json', '**/*.md', '**/*.yaml'],
  rules: {
    'no-descending-specificity': null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
    'selector-pseudo-element-no-unknown': [
      true,
      {
        ignorePseudoElements: ['v-deep'],
      },
    ],
    'selector-pseudo-class-no-unknown': [
      true,
      {
        ignorePseudoClasses: ['deep'],
      },
    ],
    // 指定样式的排序
    'order/properties-order': [
      'position',
      'top',
      'right',
      'bottom',
      'left',
      'z-index',
      'display',
      'justify-content',
      'align-items',
      'float',
      'clear',
      'overflow',
      'overflow-x',
      'overflow-y',
      'padding',
      'padding-top',
      'padding-right',
      'padding-bottom',
      'padding-left',
      'margin',
      'margin-top',
      'margin-right',
      'margin-bottom',
      'margin-left',
      'width',
      'min-width',
      'max-width',
      'height',
      'min-height',
      'max-height',
      'font-size',
      'font-family',
      'text-align',
      'text-justify',
      'text-indent',
      'text-overflow',
      'text-decoration',
      'white-space',
      'color',
      'background',
      'background-position',
      'background-repeat',
      'background-size',
      'background-color',
      'background-clip',
      'border',
      'border-style',
      'border-width',
      'border-color',
      'border-top-style',
      'border-top-width',
      'border-top-color',
      'border-right-style',
      'border-right-width',
      'border-right-color',
      'border-bottom-style',
      'border-bottom-width',
      'border-bottom-color',
      'border-left-style',
      'border-left-width',
      'border-left-color',
      'border-radius',
      'opacity',
      'filter',
      'list-style',
      'outline',
      'visibility',
      'box-shadow',
      'text-shadow',
      'resize',
      'transition',
    ],
  },
}

十五、.setting.json 风格统一

  • 非必须

可以在自己的项目下面建立 vscode 的特殊配置文件,保证风格统一