vue3-admin项目搭建
项目初始化
创建 git 仓库
npm 管理工具
pnpm 安装
pnpm create vite zf-v3-admin –template vue-ts
pnpm init 初始化package.json
创建pnpm-workspace.yaml 定义工作区
pnpm-lock.yaml 和 package-lock.json 都是项目中的锁定文件,它们的作用是锁定项目所依赖的包的版本。
pnpm-lock.yaml 是由 pnpm(一个 JavaScript 包管理工具)生成的锁定文件,它记录了所有项目依赖的包的版本。
package-lock.json 是 NPM(Node Package Manager,一个 JavaScript 包管理工具)生成的锁定文件,它也记录了所有项目依赖的包的版本。
代码风格统一
vue3+ts+vite项目中使用eslint+prettier+stylelint+husky指南 - 掘金
配置ESLint + Prettier
安装对应包
pnpm add eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-prettier eslint-config-prettier prettier eslint-plugin-vue @vue/eslint-config-prettier @vue/eslint-config-typescript -Dw
根目录创建以下配置文件以及内容如下
-
.esintrc.js
:基础eslint配置,支持ts,结合prettiermodule.exports = { root: true, env: { browser: true, node: true, es6: true, }, extends: [ "plugin:vue/vue3-essential", "eslint:recommended", "@vue/typescript/recommended", "@vue/prettier", "@vue/eslint-config-typescript", ], parser: "vue-eslint-parser", parserOptions: { parser: "@typescript-eslint/parser", ecmaVersion: 2020, sourceType: "module", jsxPragma: "React", ecmaFeatures: { jsx: true, tsx: true, }, }, rules: { // TS "@typescript-eslint/no-explicit-any": "off", "no-debugger": "off", "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/ban-types": "off", "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-unused-vars": [ "error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_", }, ], "no-unused-vars": [ "error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_", }, ], // Vue "vue/no-v-html": "off", "vue/require-default-prop": "off", "vue/require-explicit-emits": "off", "vue/multi-word-component-names": "off", "vue/html-self-closing": [ "error", { html: { void: "always", normal: "always", component: "always", }, svg: "always", math: "always", }, ], // Prettier "prettier/prettier": [ "error", { endOfLine: "auto", }, ], }, };
-
eslintrc.vue3.js
:vue3项目的eslint配置,工作区内的vue3项目可都引用此配置/* eslint-env node */ require('@rushstack/eslint-patch/modern-module-resolution') module.exports = { root: true, extends: [ './.eslintrc.js', 'plugin:vue/vue3-recommended', '@vue/eslint-config-typescript', // 如需跳过prettier改成'@vue/eslint-config-prettier/skip-formatting', '@vue/eslint-config-prettier', ], plugins: [ // eslint-plugin-vue 缩写 // plugin:vue/vue3-recommended->basse内部plugins配置了vue插件, // 同样这里不配置vue也行 'vue' ], }
-
.prettier.config.js
/** 配置项文档:https://prettier.io/docs/en/configuration.html */ module.exports = { /** 每一行的宽度 */ printWidth: 120, /** Tab 键的空格数 */ tabWidth: 2, /** 在对象中的括号之间是否用空格来间隔 */ bracketSpacing: true, /** 箭头函数的参数无论有几个,都要括号包裹 */ arrowParens: "always", /** 换行符的使用 */ endOfLine: "auto", /** 是否采用单引号 */ singleQuote: false, /** 对象或者数组的最后一个元素后面不要加逗号 */ trailingComma: "none", /** 是否加分号 */ semi: false, /** 是否使用 Tab 格式化 */ useTabs: false, };
-
.eslintignore
.prettierignore
node_modules/* dist/* asset/* *.d.ts
-
package.json添加执行脚本
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,tests,types}/**/*.{vue,js,jsx,ts,tsx}\" --fix", "lint:prettier": "prettier --write \"{src,tests,types}/**/*.{vue,js,jsx,ts,tsx,json,css,less,scss,html,md}\"", "lint": "pnpm lint:eslint && pnpm lint:prettier",
Ps:配置好后会报错 Prettier 3.0.0 TypeError: prettier.resolveConfig.sync is not a function
原因是:prettier 和eslint两个包的冲突
配置Stylelint + Prettier
Stylelint 和 ESLint的配置是极为相似的
-
安装
pnpm add stylelint stylelint-config-standard stylelint-config-standard-scss stylelint-config-standard-vue stylelint-config-prettier stylelint-order stylelint-prettier -Dw
-
根目录创建配置文件
-
stylelint.config.js
module.exports = { extends: [ "stylelint-config-standard-scss", "stylelint-config-standard-vue/scss", "stylelint-prettier/recommended", "stylelint-config-prettier", ], plugins: ["stylelint-order", "stylelint-prettier"], rules: { "order/properties-order": [ "display", "position", "float", "top", "right", "bottom", "left", "z-index", "width", "height", "max-width", "max-height", "min-width", "min-height", "padding", "padding-top", "padding-right", "padding-bottom", "padding-left", "margin", "margin-top", "margin-right", "margin-bottom", "margin-left", "margin-collapse", "margin-top-collapse", "margin-right-collapse", "margin-bottom-collapse", "margin-left-collapse", "overflow", "overflow-x", "overflow-y", "clip", "clear", "font", "font-family", "font-size", "font-smoothing", "osx-font-smoothing", "font-style", "font-weight", "line-height", "letter-spacing", "word-spacing", "color", "text-align", "text-decoration", "text-indent", "text-overflow", "text-rendering", "text-size-adjust", "text-shadow", "text-transform", "word-break", "word-wrap", "white-space", "vertical-align", "list-style", "list-style-type", "list-style-position", "list-style-image", "pointer-events", "cursor", "background", "background-color", "border", "border-radius", "content", "outline", "outline-offset", "opacity", "filter", "visibility", "size", "transform", ], }, };
-
.stylelintignore
node_modules/* dist/* public/*
-
package.json添加执行脚本
{ "scripts":{ // ... "lint:css": "stylelint **/*.{vue,css,sass,scss} --fix", } }
-
husky + lint-staged 规范提交信息,执行格式校验
-
git 仓库
-
安装
pnpm add husky lint-staged @commitlint/config-conventional @commitlint/cli -Dw
-
创建.husky文件夹(执行husky安装命令)
pnpx husky install
-
添加钩子
-
pre-commit:
pnpx husky add .husky/pre-commit "npx lint-staged"
-
commit-msg:
pnpx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
-
-
修改packages.json,配置提交前需要执行的脚本。
{ "scripts": { "dev": "vite", "build:stage": "vue-tsc --noEmit && vite build --mode staging", "build:prod": "vue-tsc --noEmit && vite build", "preview:stage": "pnpm build:stage && vite preview", "preview:prod": "pnpm build:prod && vite preview", "lint:eslint": "eslint --cache --max-warnings 0 \"{src,tests,types}/**/*.{vue,js,jsx,ts,tsx}\" --fix", "lint:prettier": "prettier --write \"{src,tests,types}/**/*.{vue,js,jsx,ts,tsx,json,css,less,scss,html,md}\"", "lint": "pnpm lint:eslint && pnpm lint:prettier", "prepare": "husky install", "test": "vitest" }, "lint-staged": { "*.{vue,js,jsx,ts,tsx}": [ "eslint --fix", "prettier --write" ], "*.{css,less,scss,html,md}": [ "prettier --write" ], "package.json": [ "prettier --write" ] }, }
-
项目根目录新建
commitlint.config.js
配置文件,规范提交信息。参考配置如下// commitlint.config.js module.exports = { extends: ['@commitlint/config-conventional'], rules: { /** 配置含义 name: [ 等级( 0: disable,1: warning, 2: error ), 生效与否( always, never ), 值 ] */ 'type-enum': [ 2, 'always', [ 'upd', // 更新某功能 'feat', // 新增功能(feature) 'fix', // 修复补丁(bug) 'refactor', // 代码重构,未新增任何功能和修复任何 bug 'docs', // 修订文档 'style', // 仅调整空格、格式缩进等(不改变代码逻辑的变动) 'test', // 测试用例的增加/修改 'perf', // 优化相关,改善性能和体验的修改 'chore', // 构建过程和辅助工具的变动 'merge', // 合并分支或冲突等 'revert', // 回滚到上一个版本 'build', //改变构建流程,新增依赖库、工具等(例如 webpack、maven 修改) 'ci' //自动化流程配置修改 ], ], 'type-case': [0], 'type-empty': [0], 'scope-empty': [0], 'scope-case': [0], 'subject-full-stop': [0, 'never'], 'subject-case': [0, 'never'], 'header-max-length': [0, 'always', 72], }, }
自动生成changelog及项目版本管理
-
版本管理
-
version类别介绍
每个npm包中都有一个package.json文件,如果要发包的话,package.json中的version就是版本号了。
version字段结构为:'0.0.0-0’
分别代表:大号.中号.小号-预发布号,对应majon.minor.patch-prerelease
npm中version的类别及描述:
-
major
- 如果没有预发布号,则直接升级一位大号,其他位都置为0
- 如果有预发布号
-
- 中号和小号都为0,则不升级大号,而将预发布号删掉。即2.0.0-1变成2.0.0,这就是预发布的作用
-
- 如果中号和小号有任意一个不是0,那边会升级一位大号,其他位都置为0,清空预发布号。即 2.0.1-0变成3.0.0
-
-
minor
- 如果没有预发布号,则升级一位中号,大号不动,小号置为空(2.0.1⇒ 2.1.0)
- 如果有预发布号:
- 如果小号为0,则不升级中号,将预发布号去掉
- 如果小号不为0,同理没有预发布号
-
patch
- 如果没有预发布号:直接升级小号,去掉预发布号
- 如果有预发布号:去掉预发布号,其他不动
-
premajor
- 直接升级大号,中号和小号置为0,增加预发布号为0
-
preminor
- 直接升级中号,小号置为0,增加预发布号为0
-
prepatch
- 直接升级小号,增加预发布号为0
-
prerelease
- 如果没有预发布号:增加小号,增加预发布号为0
- 如果有预发布号,则升级预发布号
-
-
-
生成 Change log
pnpm add conventional-changelog-cli -w
package.json
{ "scripts": { "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", } }
项目基础基本设置
-
TS 配置修改
- 创建完成后
tsconfig.json
中有几处配置会爆红,需要修改配置
{ "compilerOptions": { "target": "esnext", /** https://cn.vitejs.dev/guide/features.html#typescript-compiler-options */ "useDefineForClassFields": true, "module": "esnext", "moduleResolution": "node", /** TS 严格模式 */ "strict": true, "jsx": "preserve", "importHelpers": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "sourceMap": true, "resolveJsonModule": true, /** https://cn.vitejs.dev/guide/features.html#typescript-compiler-options */ "isolatedModules": true, "esModuleInterop": true, "lib": ["esnext", "dom"], "skipLibCheck": true, "types": [ "node", "vite/client", /** Element Plus 的 Volar 插件支持 */ "element-plus/global", "vitest" ], /** baseUrl 用来告诉编译器到哪里去查找模块,使用非相对模块时必须配置此项 */ "baseUrl": ".", /** 非相对模块导入的路径映射配置,根据 baseUrl 配置进行路径计算 */ "paths": { "@/*": ["src/*"] } }, "include": [ "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "types/**/*.d.ts", "vite.config.ts", "vitest.config.ts" ], /** 编译器默认排除的编译文件 */ "exclude": ["node_modules", "dist"] }
- 创建完成后
-
配置路径别名@
-
安装@types/node
pnpm add -D @types/node -w
-
修改vite.config.ts配置路径别名@
import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import path from "path"; //这个path用到了上面安装的@types/node // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue()], //这里进行配置别名 resolve: { alias: { "@": path.resolve("./src"), // @代替src }, }, });
-
修改tsconfig.json 中的信息
"compilerOptions": { //添加如下信息 "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录 "paths": {"@/*": ["src/*"]} // 路径映射,相对于baseUrl },
-
-
端口代理配置
vite.config.ts 文件
export default defineConfig({ server: { /** 是否开启 HTTPS */ https: false, /** 设置 host: true 才可以使用 Network 的形式,以 IP 访问项目 */ host: true, // host: "0.0.0.0" /** 端口号 */ port: 3333, /** 是否自动打开浏览器 */ open: false, /** 跨域设置允许 */ cors: true, /** 端口被占用时,是否直接退出 */ strictPort: false, /** 接口代理 */ proxy: { "/api/v1": { target: "https://mock.mengxuegu.com/mock/63218b5fb4c53348ed2bc212/api/v1", ws: true, /** 是否允许跨域 */ changeOrigin: true, rewrite: (path) => path.replace("/api/v1", ""), }, }, }, })
-
bulid 配置
vite.config.ts 文件
export default defineConfig({ build: { /** 消除打包大小超过 500kb 警告 */ chunkSizeWarningLimit: 2000, /** Vite 2.6.x 以上需要配置 minify: "terser", terserOptions 才能生效 */ minify: "terser", /** 在打包代码时移除 console.log、debugger 和 注释 */ terserOptions: { compress: { drop_console: false, drop_debugger: true, pure_funcs: ["console.log"], }, format: { /** 删除注释 */ comments: false, }, }, /** 打包后静态资源目录 */ assetsDir: "static", }, })
安装element-plus(ui组件库 Ant Design Vue同理)
PS: 因为Ant Design Vue table ,定义好 columns 有几列后,用 template 写法就无法用 v-if 去隐藏某一列()
Element Plus 和 Ant Design Vue 的分析对比 - 掘金
-
安装less/scss
-
安装less
pnpm add -D less less-loader -w
-
安装scss
pnpm add -D less sass node-sass -w
ps: 留个小坑下面是解决方案
Human verification
-
-
安装element-plus(推荐按需导入和手动导入)
安装 | Element Plus
-
导入element 的图标集合
pnpm install @element-plus/icons-vue -w
全局注册: 创建plugin然后创建element-plus-icon目录增加index.ts
import { type App } from "vue" import * as ElementPlusIconsVue from "@element-plus/icons-vue" export function loadElementPlusIcon(app: App) { /** 注册所有 Element Plus Icon */ for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } }
使用:
<script setup lang="ts"> import { Setting } from "@element-plus/icons-vue"; </script> <template> <Setting /> </template> <style scoped> </style>
-
安装pinia
Pinia 🍍
-
导包
pnpm install -D pinia -w
-
创建pinia,在
src/stroes
目录下创建index.ts
文件,添加下面的内容:import { createPinia } from "pinia" const store = createPinia() export default store
-
在
main.ts
中注册piniaimport { createApp } from "vue"; import "./style.css"; import App from "@/App.vue"; import store from "@/store"; const app = createApp(App); app.use(store).mount("#app");
-
在src下创建一个 store/user.ts 文件
什么名字可以,因为pinia它有一个根文件,会把 defineStore 第一个参数当id值,相当于vuex中的 module 自动引入,也会在Vue.js devtools 插件中以第一个参数名展示(下面展示)
注意:defineStore第一个参数很重要,而且是唯一值。它的命名能让开发者在Devtools快速找到相关数据,方便调试
import { defineStore } from "pinia"; export const useUserStore = defineStore("main", { id: "app-user", // 相当于data state: () => { return { // 所有这些属性都将自动推断其类型,如果推断失败可以试下 as xxx counter: 0, name: "Eduardo", }; }, // 相当于计算属性 getters: { doubleCount: (state) => { return state.counter * 2; }, }, // 相当于vuex的 mutation + action,可以同时写同步和异步的代码 actions: { increment() { this.counter++; }, randomizeCounter() { setTimeout(() => { this.counter = Math.round(100 * Math.random()); }, 0); }, }, });
-
HelloWorld.vue中使用
<script setup lang="ts"> import { ref } from "vue"; import { Setting } from "@element-plus/icons-vue"; //引入想要的pinia文件 {} 里面就是对应导出的名字 import { useUserStore } from "@/store/modules/user"; import { storeToRefs } from "pinia"; const User = useUserStore(); // 解构main里面的state和getters的数据, // 使用storeToRefs解构才有响应式,响应式可以直接修改数据,这里只用来渲染 let { counter, name, doubleCount } = storeToRefs(User); defineProps<{ msg: string }>(); const count = ref(0); //(常用方法三种) //常用方法一: 使用数据 console.log(counter); //使用方法(方法目前不能解构) User.increment(); // 常用方法二:修改数据 counter = 9999; //常用方法三: //进阶使用$patch,多个修改 const amend = () => { User.$patch((state) => { state.counter += 10; state.name = "张三"; }); }; </script> <template> <h1>{{ msg }}</h1> <Setting /> <div>counter:{{ counter }}</div> <div>doubleCount:{{ doubleCount }}</div> <el-button @click="User.randomizeCounter()">counter(round)</el-button> <el-button type="primary" @click="User.increment()">counter++</el-button> <div>{{ name }}</div> <el-button @click="amend()">修改</el-button> <div class="card"> <button type="button" @click="count++">count is {{ count }}</button> <p> Edit <code>components/HelloWorld.vue</code> to test HMR </p> </div> <p> Check out <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank" >create-vue</a >, the official Vue + Vite starter </p> <p> Install <a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a> in your IDE for a better DX </p> <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p> </template> <style scoped> .read-the-docs { color: #888; } </style>
结果如图:
像使用组件一样使用 SVG 图片
-
安装vite-svg-loader:
pnpm install -D vite-svg-loader -w
-
vite.config.ts
增加配置import svgLoader from "vite-svg-loader" export default defineConfig({ plugins: [ svgLoader(), ] })
-
使用
import Svg404 from "@/assets/error-page/404.svg?component"; <Svg404 />
unocss CSS框架
-
安装
pnpm install -D unocss -w
-
vue.config.js 中对Unocss进行如下配置
import UnoCSS from "unocss/vite"; export default defineConfig({ plugins: [ vue(), /** 将 SVG 静态图转化为 Vue 组件 */ svgLoader({ defaultImport: "url" }), /** SVG */ createSvgIconsPlugin({ iconDirs: [path.resolve(process.cwd(), "src/icons/svg")], symbolId: "icon-[dir]-[name]", }), /** UnoCSS */ UnoCSS(),] })
-
main.js
中按这种方式引入:import 'uno.css
import 'uno.css