Vite6+Vue3.5+TS+Element-Plus打造企业级前端项目模版

1,234 阅读8分钟

通过这篇文章可以学到

  • 🚀 使用 Vite 快速搭建 Vue3 企业级项目架构
  • 🔍 配置 Prettier + ESLint 实现代码规范化
  • 🔒 通过 Git Hooks 实现提交规范管控
  • 🧩 集成 Element-Plus、Pinia、Vue Router 等核心库

项目模版源码

GitHub地址

依赖版本

  • vite:6.1.0
  • vue:3.5.13
  • typescript:5.7.2
  • eslint:8.57.0
  • prettier:3.5.3
  • element-plus:2.9.5
  • pinia:3.0.1
  • vue-router:4.5.0
  • sass:1.85.1
  • axios:1.8.1
  • husky:9.1.7
  • commitizen:4.3.1

1、新建项目

为什么选择 pnpm?

  • 比 npm/yarn 更快的安装速度
  • 磁盘空间节省高达 50%
  • 严格的依赖管理避免幽灵依赖
pnpm create vite project --template vue-ts

2、基础配置

  • 添加@types/node,使得在使用 TypeScript 时可以获得更好的类型检查和智能提示
pnpm install @types/node -D
  • tsconfig.app.json
{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,

    "baseUrl": ".", //查询的基础路径
    "paths": { "@/*": ["src/*"] }, //路径映射,配合别名使用
    "types": ["node", "element-plus/global"],

    // 使用jsx才需要配置
    "jsx": "preserve",
    "jsxImportSource": "vue"
  },
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}
  • vite.config.ts
import path from 'path';

import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';

const pathSrc = path.resolve(__dirname, 'src');

const fePort = 1118;
const serverOrigin = 'http://localhost:1110';

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue()
  ],
  // 服务器设置
  server: {
    cors: true, // 默认启用并允许·任何源
    host: '0.0.0.0', // 指定服务器主机名
    port: fePort, // 指定服务端口号
    open: true, // 运行自动打开浏览器
    strictPort: true, // 若3030端口被占用,直接结束项目
    proxy: {
      '/api': {
        target: serverOrigin,
        changeOrigin: true,
        secure: false // 忽略自签名证书
      }
    }
  },
  //这里进行配置别名
  resolve: {
    alias: {
      '@': pathSrc // @代替src
    }
  },
});

3、代码规范

3.1、EditorConfig

  • 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
quote_type = single

3.2、ESLint

安装

pnpm install eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-import -D

使用

  • 创建配置文件.eslintrc
{
  "env": {
    "browser": true,
    "node": true,
    "es2021": true
  },
  "parser": "vue-eslint-parser",
  "extends": [
    "eslint:recommended",
    "plugin:vue/vue3-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "eslint-config-prettier"
  ],
  "parserOptions": {
    "ecmaVersion": "latest",
    "parser": "@typescript-eslint/parser",
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "plugins": ["vue", "@typescript-eslint", "prettier", "import"],
  "rules": {
    "eqeqeq": 2, // 严格等号逻辑
    "no-unused-vars": 1, // 未使用的变量进行警告
    "arrow-parens": [0, "always"], // 箭头函数参数应该始终包含括号
    "arrow-body-style": [1, "as-needed"], // 箭头函数可以去掉括号就去掉括号
    "prettier/prettier": ["error", { "semi": true }],
    "vue/multi-word-component-names": "off", // 禁用vue文件强制多个单词命名
    "@typescript-eslint/no-explicit-any": ["off", {}], //允许使用any
    "@typescript-eslint/no-this-alias": [
      "error",
      {
        "allowedNames": ["that"] // this可用的局部变量名称
      }
    ],
    "@typescript-eslint/ban-ts-comment": "off", //允许使用@ts-ignore
    "@typescript-eslint/no-non-null-assertion": "off", //允许使用非空断言
    "no-console": [
      //提交时不允许有console.log
      "warn",
      {
        "allow": ["warn", "error"]
      }
    ],
    "no-debugger": "warn", //提交时不允许有debugger
    "vue/comment-directive": "off",
    // 确保没有重复引入
    "import/no-duplicates": "error",
    // 确保 import 语句按照组分类并排序
    "import/order": [
      "error",
      {
        // builtin:内置模块;external:第三方模块;internal:内部引用;sibling:兄弟依赖;parent:父节点依赖;index:index 文件依赖;unknown:未知依赖
        "groups": [
          "builtin",
          "external",
          "internal",
          "parent",
          "sibling",
          "index",
          "object",
          "type",
          "unknown"
        ],
        // vue的引入将会排在第一位
        "pathGroups": [
          {
            "pattern": "vue",
            "group": "external",
            "position": "before"
          }
        ],
        "pathGroupsExcludedImportTypes": ["builtin"],
        // newlines-between 不同组之间是否进行换行
        "newlines-between": "always",
        // alphabetize 根据字母顺序对每个组内的顺序进行排序
        "alphabetize": {
          "order": "asc",
          "caseInsensitive": true
        }
      }
    ]
  }
}
  • 创建忽略文件.eslintignore
# eslint 忽略检查 (根据项目需要自行添加)
node_modules
dist

3.3、Prettier

安装

pnpm i prettier eslint-config-prettier eslint-plugin-prettier -D

使用

  • 创建配置文件.prettierrc
{
  "useTabs": false,
  "tabWidth": 2,
  "printWidth": 80,
  "singleQuote": true,
  "trailingComma": "none",
  "semi": true,
  "arrowParens": "always",
  "bracketSpacing": true
}
  • 创建忽略.prettierignore
# 忽略格式化文件 (根据项目需要自行添加)
node_modules
dist

4、提交规范

4.1、husky+lint-staged

安装

pnpm install husky lint-staged -D

使用

  • 初始化
pnpm exec husky init
  • .husky/pre-commit
pnpm --no-install lint-staged
  • package.json
"lint-staged": {
    "src/**/*.{vue,js,jsx,ts,tsx,json}": [
      "pnpm run lint",
      "prettier --write"
    ]
  }

4.2、commitizen

安装

pnpm install -D commitizen cz-conventional-changelog @commitlint/config-conventional @commitlint/cli commitlint-config-cz cz-customizable

使用

> git-cz

cz-cli@4.3.1, cz-customizable@7.4.0

Unable to find a configuration file. Please refer to documentation to learn how to set up: https://github.com/leonardoanalista/cz-customizable#steps "
Cannot read properties of null (reading 'subjectLimit')

如遇以上错误,是由于package.json指定了"type": "module",且cz不支持ES Module,需要使用.cjs作为文件类型,并重新指定路径名

  • 创建文件.cz-config.cjs
module.exports = {
  types: [
    { value: 'init', name: 'init:     初始提交' },
    { value: 'feat', name: 'feat:     新特性、新功能' },
    { value: 'fix', name: 'fix:      修复bug' },
    { value: 'docs', name: 'docs:     文档变更' },
    { value: 'style', name: 'style:    样式修改不影响逻辑' },
    { value: 'perf', name: 'perf:     优化相关,比如提升性能、体验' },
    { value: 'refactor', name: 'refactor: 代码重构' },
    { value: 'test', name: 'test:     添加测试' },
    { value: 'chore', name: 'chore:    更改配置文件' },
    {
      value: 'build',
      name: 'build:    编译相关的修改,例如发布版本、对项目构建或者依赖的改动'
    },
    { value: 'ci', name: 'ci:       持续集成修改' },
    { value: 'revert', name: 'revert:   回滚到上一个版本' },
    { value: 'merge', name: 'merge:    合并其他分支代码' }
  ],
  messages: {
    type: '选择一种你的提交类型:',
    scope: '选择一个scope (可选):',
    // 如果allowcustomscopes为true,则使用
    customScope: '请输入修改范围(可选):',
    subject: '请输入简要描述(必填):',
    body: '详细描述. 使用"|"换行:\n',
    breaking: 'Breaking Changes列表:\n',
    footer: '关闭的issues列表. E.g.: #31, #34:\n',
    confirmCommit: '确认提交?'
  },
  allowCustomScopes: true,
  allowBreakingChanges: ['feat', 'fix'],
  skipQuestions: ['breaking', 'footer']
};
  • 创建文件commitlint.config.cjs
module.exports = {
  extends: ['@commitlint/config-conventional', 'cz'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'init', // 初始化
        'feat', // 新功能(feature)
        '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, 'never'],
    // <scope> 范围格式
    'scope-case': [0],
    // <subject> 主要 message 不能为空
    'subject-empty': [2, 'never']
  }
};
  • 创建文件.husky/commit-msg
pnpm --no-install commitlint --edit "$1"
  • package.json文件中
"scripts": {
  "prepare": "husky",
  "commit": "node_modules/cz-customizable/standalone.js",
},
  "config": {
    "commitizen": {
      "path": "node_modules/cz-customizable"
    },
    "cz-customizable": {
      "config": "./.cz-config.cjs"
    }
  },
  • pnpm run commit,最后如下图

4、Element-plus

安装

pnpm install element-plus @element-plus/icons-vue
pnpm install -D unplugin-vue-components unplugin-auto-import

使用

  • main.ts
import { createApp } from 'vue';

// 全部引入
// import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
// eslint-disable-next-line import/order
import * as ElementPlusIconsVue from '@element-plus/icons-vue';

import App from './App.vue';

const app = createApp(App);

for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component);
}
// app.use(ElementPlus);

app.mount('#app');
  • 配置按需引入
import path from 'path';

import vue from '@vitejs/plugin-vue';
import AutoImport from 'unplugin-auto-import/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';
import { defineConfig } from 'vite';

const pathSrc = path.resolve(__dirname, 'src');

const fePort = 1118;
const serverOrigin = 'http://localhost:1110';

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
      resolvers: [ElementPlusResolver()]
    }),
    Components({
      resolvers: [
        // 自动导入 Element Plus 组件
        ElementPlusResolver()
      ]
    })
  ],
  // 服务器设置
  server: {
    cors: true, // 默认启用并允许·任何源
    host: '0.0.0.0', // 指定服务器主机名
    port: fePort, // 指定服务端口号
    open: true, // 运行自动打开浏览器
    strictPort: true, // 若3030端口被占用,直接结束项目
    proxy: {
      '/api': {
        target: serverOrigin,
        changeOrigin: true,
        secure: false // 忽略自签名证书
      }
    }
  },
  //这里进行配置别名
  resolve: {
    alias: {
      '@': pathSrc // @代替src
    }
  }
});

5、Pinia

安装

pnpm install pinia

使用

  • main.ts中使用pinia
import { createApp } from 'vue';

import { createPinia } from 'pinia';

import App from './App.vue';

const app = createApp(App);

const pinia = createPinia();
app.use(pinia);

app.mount('#app');
  • 新建src/store文件夹,新建index.ts文件
// Option Store
import { defineStore } from 'pinia';

export const useStore = defineStore('store', {
  state: () => ({
    username: '晚风予星',
    count: 0
  }),
  // 相当于Vue中的计算属性,具有缓存属性,值不改变多次使用,只调用一次
  // 箭头函数第一个参数就是state
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  // 方法 支持同步和异步
  actions: {
    increment() {
      this.count++
    },
  }
});

// Setup Store
export const useStore = defineStore('store', () => {
  const username = ref('晚风予星')
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { username, count, doubleCount, increment }
})

Setup Store 中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions
  • 使用
<script setup lang="ts">
import { ref } from 'vue';

import { useStore } from '@/store/index';

const store = useStore();
</script>

<template>
  <div>{{ store.username }}</div>
</template>

<style lang="scss" scoped>
</style>
  • 和在 Vue 中如何选择组合式 API 与选项式 API 一样,选择你觉得最舒服的那一个就好。两种语法都有各自的优势和劣势。Option Store 更容易使用,而 Setup Store 更灵活和强大。

6、Vue-Router

安装

pnpm install vue-router

使用

  • src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';

import type { RouteRecordRaw } from 'vue-router';

import * as PAGE_URL from '@/constants/page-url-constants';

const routes: RouteRecordRaw[] = [];

const router = createRouter({
  history: createWebHistory(),
  routes
});
// 添加全局前置守卫
router.beforeEach((to, from, next) => {
  next();
});

router.afterEach((to) => {
  // 修改当前标签页的名称
  const title = to.meta.title;
  const titleNode = document.querySelector('head > title');
  if (title && titleNode) {
    titleNode.textContent = title.toString();
  }
});

export default router;
  • main.ts
import { createApp } from 'vue';

import App from './App.vue';

import router from '@/router';

const app = createApp(App);

app.use(router);

app.mount('#app');

7、SCSS

安装

pnpm install sass
  • vite配置导入scss全局变量文件
css: {
  preprocessorOptions: {
    scss: {
      additionalData: `@use "@/assets/css/mixins.scss" as *;`
    }
  }
}

8、axios

安装

pnpm install axios

使用

  • utils/request.ts
import axios from 'axios';

import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios';

const request: AxiosInstance = axios.create({});

// request interceptor
request.interceptors.request.use(
  (config): InternalAxiosRequestConfig => config,
  (error) => Promise.reject(error)
);

// response interceptor
request.interceptors.response.use(
  (response) => {
    // 只要状态码是这些之一就认为是成功的响应
    if (
      response.status === 200 ||
      response.status === 201 ||
      response.status === 304
    ) {
      const data: any = response.data;

      if (data) {
        return data;
      } else {
        return Promise.reject(data);
      }
    } else {
      return Promise.reject(response);
    }
  },
  async (error) => Promise.reject(error)
);
export default request;
  • service文件中
import request from '@/utils/request';

export function handleTest(type: string) {
  return request.get('/api/paint/test', { params: { type } });
}

export function handleHttpRequest(path: string) {
  return request.post('/api/request', {
    path
  });
}

9、配置打包分类输出

  • vite.config.ts
build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vue: ['vue', 'pinia', 'vue-router']
        },
        chunkFileNames: 'static/js/[name]-[hash].js',
        entryFileNames: 'static/js/[name]-[hash].js',
        assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
      }
    }
  }

10、配置使用TSX

安装

pnpm install @vitejs/plugin-vue-jsx -D

使用

  • vite.config.ts
import path from 'path';

import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import { defineConfig } from 'vite';

const pathSrc = path.resolve(__dirname, 'src');

const fePort = 1118;
const serverOrigin = 'http://localhost:1110';

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueJsx(),
  ],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vue: ['vue', 'pinia', 'vue-router']
        },
        chunkFileNames: 'static/js/[name]-[hash].js',
        entryFileNames: 'static/js/[name]-[hash].js',
        assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
      }
    }
  }
});
  • Hello.tsx
import { defineComponent } from 'vue';
const Hello = defineComponent({
  name: 'Hello',
  props: {
    msg: {
      type: String,
      default: 'Hello TSX'
    }
  },
  setup(props) {
    return () => <div>{props.msg}</div>;
  }
});

export default Hello;

最后

感谢大家观看到最后,若有错误,望指出,记得点赞关注加收藏哦 ~