一篇文章试图教会你,搭建可用的 Vue3 企业版项目脚手架模板

lxf2023-05-09 08:41:01

距离上一次,脚手架文章《手把手Vue3项目(一)》已经过去了10个月,

这一次,随着Vue3正式普及,同时也能让更多的小伙伴,快速进入Vue3项目的开发,

这里我重新搭建了一个Vue3脚手架模板,并试图教会大家如何安装和配置一个Vue3的项目,

其中涉及几乎一个企业项目所包含的所有内容,希望可以帮到大家,

此项目可以从gitee上clone后直接使用,地址在文末

使用手册

1. cd vue3-scaffold-template
2. pnpm install 或 npm install
3. pnpm dev 或 npm run dev 启动项目
4. pnpm jsondb 或 npm run jsondb 启动json-server服务器
​
有两个案例,配置管理、时间管理App供大家参考。

安装和配置过程

授人以鱼不如授人以渔,

这个脚手架,大家可以clone下来,直接进入项目开发,希望大家用的开心。

创建项目

pnpm create vite

然后按照提示操作即可!

这里我选择的是:Vue、TypeScript,Vite 目前默认Vue3

配置src目录为默认目录

vite.config.ts 中配置

resolve: {
    alias: [{ find: '@', replacement: path.resolve(__dirname, './src') }]
  },

安装Element-Plus

参照 Element-Plus 快速上手

  1. 安装 Element-Plus
pnpm add element-plus
  1. 需要先安装按需导入的两款插件
pnpm add unplugin-vue-components unplugin-auto-import -D
  1. vite.config.ts 中配置
// 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. 在实际代码中测试,是否生效

安装Element Plus 图标

  1. 安装Element Icon图标
pnpm install @element-plus/icons-vue

自动导入图标(可以使用,但考虑到项目开发,本人弃用此方案)

从 iconify(图标网站) 中自动导入任何图标集

自动导入必须遵循名称格式 {prefix:默认为i}-{collection:图标集合的名称}-{icon:图标名称}

<el-icon color="#000" size="22">

<i-ep-expand />

</el-icon>

  1. 需要先安装按需导入的两款插件
pnpm add unplugin-icons unplugin-auto-import -D
  1. vite.config.ts 中配置
// vite.config.ts
import path from 'path'
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// 给插件开发者调试用的插件,个人使用没必要安装和使用
import Inspect from 'vite-plugin-inspect'const pathSrc = path.resolve(__dirname, 'src')
​
export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      // Auto import functions from Vue, e.g. ref, reactive, toRef...
      // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
      imports: ['vue'],
​
      // Auto import functions from Element Plus, e.g. ElMessage, ElMessageBox... (with style)
      // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
      resolvers: [
        ElementPlusResolver(),
​
        // Auto import icon components
        // 自动导入图标组件
        IconsResolver({
          prefix: 'Icon',
        }),
      ],
​
      dts: path.resolve(pathSrc, 'auto-imports.d.ts'),
    }),
​
    Components({
      resolvers: [
        // Auto register icon components
        // 自动注册图标组件
        IconsResolver({
          enabledCollections: ['ep'],
        }),
        // Auto register Element Plus components
        // 自动导入 Element Plus 组件
        ElementPlusResolver(),
      ],
​
      dts: path.resolve(pathSrc, 'components.d.ts'),
    }),
​
    Icons({
      autoInstall: true,
    }),
​
    Inspect(),
  ],
})
  1. 在实际代码中测试,是否生效
<el-icon color="#000" size="22">
  <i-ep-expand />
</el-icon>

全局注册图标(本人选用方案)

import * as ElementPlusIconsVue from '@element-plus/icons-vue'const app = createApp(App)
​
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

Eslint、Prettier、 Husky、Githooks、commit-msg

  1. 需要安装的依赖
# eslint校验相关
pnpm add eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin vue-eslint-parser  -D
# prettier格式化相关
pnpm add prettier eslint-config-prettier eslint-plugin-prettier -D
# git提交相关
pnpm add husky lint-staged -D
# commit-msg相关
pnpm add @commitlint/config-conventional @commitlint/cli -D  
  1. 相关配置

eslint

.eslint.js

// .eslint.js
module.exports = {
  root: true,
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  /* 指定如何解析语法。可以为空,但若不为空,只能配该值,原因见下文。*/
  parser: 'vue-eslint-parser',
  /* 优先级低于parse的语法解析配置 */
  parserOptions: {
    parser: '@typescript-eslint/parser', // Specifies the ESLint parser
    ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features
    sourceType: 'module', // Allows for the use of imports
    ecmaFeatures: {
      tsx: true,
      jsx: true
    },
    /* 扩展配置,加一些插件 */
    extends: [
      'plugin:vue/vue3-recommended',
      'eslint:recommended',
      'plugin:@typescript-eslint/recommended',
      'prettier/@typescript-eslint',
      'plugin:prettier/recommended'
    ],
    rules: {}
  }
};
​

.eslintignore

*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
*.zip
/public
/docs
.husky
.local
/bin
Dockerfile
/src/utils/vendor

prettier

.prettierrc.js

module.exports = {
  printWidth: 100, // 单行输出(不折行)的(最大)长度
  tabWidth: 2, // 每个缩进级别的空格数
  semi: false, // 是否在语句末尾打印分号
  singleQuote: true, // 是否使用单引号
  quoteProps: 'as-needed', // 仅在需要时在对象属性周围添加引号
  bracketSpacing: true, // 是否在对象属性添加空格
  jsxBracketSameLine: true, // 将 > 多行 JSX 元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭元素),默认false,这里选择>不另起一行
  htmlWhitespaceSensitivity: 'ignore', // 指定 HTML 文件的全局空白区域敏感度, "ignore" - 空格被认为是不敏感的
  trailingComma: 'none', // 去除对象最末尾元素跟随的逗号
  useTabs: false, // 不使用缩进符,而使用空格
  jsxSingleQuote: false, // jsx 不使用单引号,而使用双引号
  arrowParens: 'always', // 箭头函数,只有一个参数的时候,也需要括号
  rangeStart: 0, // 每个文件格式化的范围是文件的全部内容
  proseWrap: 'always', // 当超出print width(上面有这个参数)时就折行
  endOfLine: 'lf' // 换行符使用 lf
};
​

.prettierignore

package.json
/dist
.DS_Store
.eslintignore
*.png
.editorconfig
.gitignore
.prettierignore
.eslintcache
*.lock
yarn-error.log
.history
CNAME
yarn.lock
​

husky

参考链接:

  • Husky
  • commit-msg

pre-commit 代码提交前(代码格式化、修复)

  1. 配置husky
# 使用推荐的自动安装方式,这里我使用的pnpm的
npx husky-init && npm install       # npm
npx husky-init && yarn              # Yarn 1
yarn dlx husky-init --yarn2 && yarn # Yarn 2+
pnpm dlx husky-init && pnpm install # pnpm

它将设置 husky,修改package.json并创建一个pre-commit您可以编辑的示例挂钩。默认情况下,它将npm test在您提交时运行,但是你需要自己找到.husky/pre-commim文件把其中的npm test改成npm run lint-staged,并在package.json中配置提交前所需要做的操作,配置内容如下:

{
  "scripts": {
    "lint-staged": "lint-staged", // ./husky/pre-commit执行的操作
  },
  
  "lint-staged": {
    "*.{js,jsx,ts,tsx,less,scss}": [
      "prettier --write", // prettier格式化代码
      "eslint --cache --fix", // eslint修复代码
      "git add"
    ]
  }
}
  1. commit-msg配置

要使用husky add添加另一个钩子commit-msg

npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

它将在.husky目录下生成一个commit-msg文件,进行提交的commit-msg检测,如果不符合规范会给出提示;同时需要在.commitment.config.js中添加配置,下面的配置可以直接使用,也可以自定义

commitment.config.js

module.exports = {
  extends: ['@commitlint/config-conventional'], // 默认配置只有这一行,rules可以自己定义
  // 也可以自定义规则
  // ignores: [(commit) => commit.includes('init')],
  rules: {
    'body-leading-blank': [2, 'always'], //body上面要有换行
    'footer-leading-blank': [1, 'always'], //footer上面要有换行
    'header-max-length': [2, 'always', 108], //header最大108个字符
    'subject-empty': [2, 'never'], //subject位不能为null
    'type-empty': [2, 'never'], //type位不能为null
    'type-enum': [
      2,
      'always',
      [
        'feat', // 新增功能
        'fix', // bug修复
        'perf', // 性能,体验优化
        'style', // 不影响程序逻辑的代码修改(修改空白字符,格式缩进,补全缺失的分号等,没有改变代码逻辑)
        'docs', // 文档更新
        'refactor', // 重构代码(既没有新增功能,也没有修复 bug)
        'build', // 主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交
        'ci', // 主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交
        'test', // 新增测试用例或是更新现有测试
        'revert', // 回滚某个更早之前的提交
        'merge', // 分支合并 Merge branch ?of ?
        'chore' // 不属于以上类型的其他类型
        // 'wip',
        // 'workflow',
        // 'types',
        // 'release',
      ]
    ]
  }
}
​
  1. 测试
# 检测上一次提交的commit是否符合提交规范
npx commitlint --from HEAD~1 --to HEAD --verbose

Vue-Router 4

  1. 安装依赖
pnpm add vue-router@4
  1. 配置:
// router/index.ts ----------------创建路由------------------
import {createRouter, createWebHistory} from "vue-router";
import {routes} from "@/router/config";
​
const router = createRouter({
    history: createWebHistory(),
    routes
})
​
export default router
​
// router/config.ts ----------------路由路径配置文件------------
export const routes = [
    // 路由重定向
    {
        path: '/',
        redirect: '/home',
    },
    {
        path: '/home',
        // component: Home
        component: () => import("@modules/home/Index.vue")
    },
    {
        path: '/about',
        component: () => import("@modules/about/Index.vue")
    }
]
​
// main.ts -----------------使用router--------------------------
import {createApp} from 'vue'
import router from "@/router";
import App from './App.vue'
import "ant-design-vue/dist/antd.less"
// import '@ant-design/icons-vue'const app = createApp(App)
app.use(router)
app.mount('#app')
// createApp(App).use(router).mount('#app')// App.vue  ----------------在页面中渲染路由加载的页面------------------------
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out <https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup>
</script>
​
<template>
  <router-view></router-view>
</template>
​
<style>
</style>
  1. 路由权限、nprogress
// main.ts --------------------------------------------
import router from '@/router'
import './permission'
​
app.use(router)
// 挂载实例 app
app.mount('#app')
​
​
// permissson文件----------------------------------------
// 路由权限处理
import router from '@/router/index'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css'
import { RouteLocationNormalized } from 'vue-router'
NProgress.configure({ showSpinner: false })
router.beforeEach(async (to, from) => {
  NProgress.start()
  // if (to.name !== 'SsoRedirect') {
  //   await initRequest();
  // }
  if (to.path.indexOf('40') > -1) {
    to.meta = from.meta
    to.name = from.name
  }
  await handleRouterQuery(to, from)
})
router.afterEach(() => {
  NProgress.done()
  // 路由完成后做些事情
})
​
let currentPath: string
​
const Reg = /(^/user(?!/dashboard).*)/// 处理路由上的公共参数 避免手动代入
async function handleRouterQuery(to: RouteLocationNormalized, from: RouteLocationNormalized) {
  const { path, fullPath } = to
  if (!Reg.test(path)) return
  if (currentPath !== fullPath) {
    currentPath = fullPath
  }
  return
}

Pinia

  1. 安装依赖
pnpm add pinia
  1. 配置
// main.ts--------------------------------------------------------
import { createPinia } from 'pinia'
import '@/store/index'
​
app.use(createPinia())
// 挂载实例 app
app.mount('#app')
​
// @/store/index.ts,批量导入modules中的所有store--------------------
import { acceptHMRUpdate } from 'pinia'const modulesFiles = import.meta.globEager('./modules/*.ts')
Object.keys(modulesFiles).forEach((modulePath) => {
  const store = modulesFiles[modulePath]
  Object.keys(store).forEach((key) => {
    if (key.startsWith('use')) {
      if (import.meta.hot) {
        import.meta.hot.accept(acceptHMRUpdate(store[key], import.meta.hot))
      }
    }
  })
})
​
// @/store/modules/*.ts--------------------------------------------
// 一个使用例子
import { defineStore } from 'pinia'interface IState {
  topic: any
}
​
export const useMessageStore = defineStore<'message', IState>('message', {
  state: () => ({
    topic: {}
  })
})
​

Scss、tailwindcss、postcss、autoprefixer

  1. 需要的依赖
pnpm add postcss sass tailwindcss @tailwindcss/line-clamp autoprefixer -D

posts.config.js

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}
​

tailwind.config.js

module.exports = {
  content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
  theme: {
    extend: {
      colors: {
        normal: '#3693FF',
        hover: '#0068E0',
        active: '#004799',
        background: '#f4f6fa',
        title: '#333333',
        content: '#666666',
        username: '#303133',
        operate: '#959EAD'
      },
      spacing: {
        32: '32px',
        4: '4px',
        36: '36px',
        16: '16px',
        24: '24px',
        8: '8px',
        12: '12px',
        10: '10px',
        18: '18px',
        20: '20px',
        48: '48px'
      },
      borderRadius: {
        none: '0',
        small: '8px',
        large: '16px'
      },
      fontSize: {
        base: '14px',
        lg: '16px',
        '2lg': '19px',
        m: '20px',
        xl: '24px',
        '2xl': '32px',
        '3xl': '40px',
        '4xl': '48px'
      }
    }
  },
  mode: 'jit',
  plugins: [require('@tailwindcss/line-clamp')]
};
​
  1. vite.config.ts
import { defineConfig } from 'vite'export default defineConfig({
  css: {
    modules: {
      scopeBehaviour: 'local',
      generateScopedName: (name) => {
        return name
      }
    }
  }
})

axios封装和使用

# 这里请大家直接去看代码,2个文件
1. src/service/index.ts # axios封装的内容
2. src/api/index.ts  # 封装后的axios如何使用

全局参数

// 导入全局参数(需要什么全局参数可以自己去定义)
import Global from './config/global'// 注入全局参数
app.config.globalProperties.GLOBAL = Global

Element Plus 国际化

// main.ts
// Element Plus 国际化
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'// Element Plus 国际化
app.use(ElementPlus, { locale: zhCn, size: 'default' })
app.mount('#app')

Find me

Gitee:gitee.com/heyhaiyon

微信公众号:heyhaiyang

AdminJS:heyhaiyang

头条号:heyhaiyang

微信群:可以在公众号中获取,文章不让放群二维码