pnpm install element-plus
Volar 支持
如果您使用 Volar,请在 tsconfig.json
中通过 compilerOptions.type
指定全局组件类型。
// tsconfig.json
{
"compilerOptions": {
// ...
"types": ["element-plus/global"]
}
}
自动导入
pnpm install -D unplugin-vue-components unplugin-auto-import
vite.config.ts
// 导入 vite 插件
import { defineConfig } from "vite";
// 导入 vue 插件
import vue from "@vitejs/plugin-vue";
// 导入自动导入插件
import AutoImport from "unplugin-auto-import/vite";
// 导入自动注册组件的插件
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import * as path from "path";
export default defineConfig({
// 配置打包后的相对路径
base: "./",
//配置别名
resolve: {
// 需要在tsconfig.json的compilerOptions中配置paths
alias: {
"@": path.resolve("./src"), // @代替src
"~": path.resolve("./src/components"), // @代替src/components
},
},
// 配置全局样式
css: {
preprocessorOptions: {
scss: {
// 这里可以添加全局的 Sass 变量、Mixin等。首先你的有这个文件
// additionalData: `
// @import "@/styles/variables.scss";
// @import "@/styles/mixins.scss";
// `,
},
},
},
// plugins插件
plugins: [
vue(), //vue
AutoImport({
//plus按需引入
resolvers: [ElementPlusResolver()],
//引入vue 自动注册api插件
imports: ["vue", "vue-router", "pinia"], // 配置需要自动导入的库
dts: "types/auto-import.d.ts", // 自动引入生成api的地址
eslintrc: {
enabled: false, // 是否开启eslint
filepath: "./.eslintrc-auto-import.json", // eslint配置文件地址
globalsPropValue: true, // 是否开启全局变量
},
}),
Components({
//plus按需引入
resolvers: [ElementPlusResolver()],
// 配置需要将哪些后缀类型的文件进行自动按需引入
extensions: ["vue", "md"],
dts: "types/components.d.ts", //自动引入生成的组件的地址
}),
],
// 打包配置
build: {
minify: "terser",
terserOptions: {
compress: {
//生产环境时移除console
drop_console: true,
drop_debugger: true,
},
},
},
// 跨域
server: {
//使用IP能访问
host: "0.0.0.0",
port: 8888,
// 热更新
hmr: true,
//自定义代理规则
proxy: {
// 选项写法
"/api": {
// target: "https://admin.ccc.com",
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
});
Ts 报红
配置完之后会出现
1:项目根目录新建 env.d.ts
/// <reference types="vite/client" />
// 三斜线引用告诉编译器在编译过程中用types形式引入的额外的文件vite/client.d.ts,
// 此文件里面是vite帮我们定义的各种常用类型定义,比如css,图片等。
declare module "*.vue" {
import type { DefineComponent } from "vue";
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}
2:修改 tsconfig.json
tsconfig.json
{
"compilerOptions": {
"target": "ESNext", // 指定ECMAScript目标版本
"useDefineForClassFields": true, // 是否校验TypeScript数据类型
"module": "ESNext", // 生成代码的模板标准
"removeComments": true, // 是否删除注释
"outDir": "./dist", // 指定输出目录
"strict": true, // 启用所有严格类型检查选项
"jsx": "preserve", // 指定JSX代码生成
"moduleResolution": "node", // 指定模块解析策略
"esModuleInterop": true, // 允许从CommonJS模块导入默认导出
"sourceMap": true, // 生成相应的.map文件
"resolveJsonModule": true, // 允许导入.json文件
"isolatedModules": true, // 仅对每个文件进行一次转换
"lib": ["ESNext", "DOM"], // 指定要包含在编译中的库文件
"skipLibCheck": true, // 跳过对声明文件的检查
"noEmit": true, // 不生成输出文件
"baseUrl": ".", // 解析非相对模块的基地址,默认是当前目录
// 路径别名 @ #
"paths": {
"@/*": ["src/*"],
"~/*": ["src/components/*"]
},
"plugins": [
//配置 unplugin-auto-import 和 unplugin-vue-components
{
"name": "unplugin-auto-import",
"options": {
"imports": ["element-plus", "vue", "vue-router", "pinia", "echarts"],
"dts": "auto-imports.d.ts"
}
},
{
"name": "unplugin-vue-components",
"options": {
"dts": "components.d.ts",
"include": ["**/*.vue", "**/*.md"],
"resolvers": ["element-plus", "pinia", "vue", "vue-router", "echarts"]
}
}
],
// 指定要包含的类型声明文件
"types": [
"element-plus/global",
"vite/client",
"vue",
"vue-router",
"@types/node",
"pinia",
"echarts"
]
},
// include: 指定要包含在编译中的文件
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.vue",
// 解决找不到模块“./xx.vue”或其它找不到模块
"env.d.ts",
// pinia
"src/store/**/*.ts",
// element-plus 自动注册组件
"types/**/*.d.ts",
// 路由
"src/router/**/*.ts"
],
// exclude: 指定要从编译中排除的文件
"exclude": ["node_modules", "dist"],
// extends: 指定要继承的配置文件
"references": [{ "path": "./tsconfig.node.json" }]
}
Eslint 报红
比如使用了ref没有引入 eslint 爆红没引入问题,主要解决方式如下面两张图,完整的vite.config.ts在上面
在 main.ts 引入全局样式
import { createApp } from "vue";
import App from "./App.vue";
//按需引入element-plus只引入了需要的组件,但是样式文件是全局的
import "element-plus/dist/index.css";
createApp(App).mount("#app");
—————————————— 按需、自动引入 完 —————————————————
8 配置styleling
stylelint 为css的lint工具。可格式化css代码,检查css语法错误与不合理的写法,指定css书写顺序等…
由于我的项目使用的sass预处理器,因此配置的为sass相关的,项目中使用其他预处理器的可以按照该配置方法改一下就好
安装依赖
less:
pnpm install stylelint stylelint-config-prettier@8.0.2 stylelint-config-recommended-less stylelint-config-standard stylelint-config-standard-vue stylelint-order stylelint-less --save-dev
scss :
pnpm install stylelint stylelint-config-prettier@8.0.2 stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-vue stylelint-order stylelint-scss --save-dev
增加 .stylelintrc.cjs
配置文件
less:
module.exports = {
extends: [
"stylelint-config-standard",
"stylelint-config-prettier",
"stylelint-config-recommended-less",
"stylelint-config-standard-vue",
],
plugins: ["stylelint-order"],
// 不同格式的文件指定自定义语法
overrides: [
{
files: ["**/*.(less|css)"],
customSyntax: "postcss-less",
},
{
files: ["**/*.(html|vue)"],
customSyntax: "postcss-html",
},
],
// 忽略的文件
ignoreFiles: [
"**/*.js",
"**/*.jsx",
"**/*.tsx",
"**/*.ts",
"**/*.json",
"**/*.md",
"**/*.yaml",
"node_modules/**/*",
".vscode/**/*",
"dist/**/*",
// 忽略src/assets/css下的less文件
"src/assets/css/**/*.less",
// 404页面
"src/views/errors-view/not-found.vue",
],
rules: {
"no-descending-specificity": null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
"less/at-import-partial-extension": "always", // 禁止省略导入文件的扩展名
"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",
],
},
};
scss:
module.exports = {
extends: [
"stylelint-config-standard",
"stylelint-config-prettier",
"stylelint-config-recommended-scss",
"stylelint-config-standard-vue",
],
plugins: ["stylelint-order"],
// 不同格式的文件指定自定义语法
overrides: [
{
files: ["**/*.(scss|css)"],
customSyntax: "postcss-scss",
},
{
files: ["**/*.(html|vue)"],
customSyntax: "postcss-html",
},
],
// 忽略的文件
ignoreFiles: [
"**/*.js",
"**/*.jsx",
"**/*.tsx",
"**/*.ts",
"**/*.json",
"**/*.md",
"**/*.yaml",
"node_modules/**/*",
".vscode/**/*",
"dist/**/*",
// 忽略src/assets/css下的scss文件
"src/assets/css/**/*.scss",
// 404页面
"src/views/errors-view/not-found.vue",
],
rules: {
"no-descending-specificity": null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
"scss/at-import-partial-extension": "always", // 禁止省略导入文件的扩展名
"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",
],
},
};
package.json
增加命令 StyleLint:check,并增加CheckAll命令(合并检查eslint、prettier、styleLint,一键运行可以检查三个配置)
"scripts": {
"pnpm:": "pnpm install",
"dev": "vite --open",
"build": "vue-tsc && npm run ESlint:check && npm run Prettier:check && npm run StyleLint:check && vite build",
"CheckAll": "vue-tsc && npm run ESlint:check && npm run Prettier:check && npm run StyleLint:check",
"ESlint:check": "eslint . --ext .vue,.js,cjs,mjs,.ts,.cts,.mts,.jsx,.tsx --fix",
"Prettier:check": "prettier --write \"./**/*.{html,vue,ts,cts,mts,js,mjs,cjs,json,md,scss}\"",
"StyleLint:check": "stylelint \"./**/*.{css,scss,less,vue,html}\" --fix",
},
安装vscode的Stylelint插件
安装该插件可在我们保存代码时自动执行stylelint
在.vscode/settings.json
中添加一下规则
基于以上配置完整 settings.json``如下:
{
// 开启自动修复
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
// 保存的时候自动格式化
"editor.formatOnSave": true,
// 默认格式化工具选择prettier
"editor.defaultFormatter": "esbenp.prettier-vscode",
// 设置为 false,表示使用制表符进行缩进。有助于减少文件大小,空格比制表符大 4 倍
"editor.insertSpaces": false,
// 设置制表符宽度为 4。请确保.prettierrc.js中的tabWidth设置为4,和vscode 制表符设置一致
"editor.tabSize": 4,
// 启用 ESLint 插件。这意味着在 VSCode 中将会对代码进行 ESLint 检查。
"eslint.enable": true,
// 表示在保存文件时运行 ESLint 检查。每当您保存文件时,VSCode 将会自动运行 ESLint 并检查代码中的错误和警告。
"eslint.run": "onSave",
// stylelint校验的文件格式
"stylelint.validate": [
"css",
"less",
"scss"
]
}
—————————————— styleLint 完 —————————————————
9 vue-router 分模块
安装:
pnpm i vue-router -S
使用 :
新建 src/router/index.ts (路由主页)
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
/* createWebHistory 与 createWebHashHistory 的区别
createWebHistory:使用 HTML5 History API 的路由模式。注意:这种模式要玩好,还需要后台配置支持。后台不配置你本地开发没问题,一旦部署上线,刷新就会出现 404。
createWebHashHistory:使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History API 的浏览器。
详细来说,createWebHistory 是基于 HTML5 History API 的,而 createWebHashHistory 是基于 URL 的 hash 值的。
在 Vue3 中,你可以通过调用 createWebHistory 或 createWebHashHistory 函数来创建路由。
createWebHistory 监听浏览器的 history.pushState 和 history.replaceState 事件,并使用 HTML5 的 history API 来管理路由。拥有更简单的 URL,不包含 "#" 符号。
createWebHashHistory 使用浏览器的 window.location.hash 属性来管理路由。在 URL 中将使用 "#" 符号,例如:`http://localhost:300/#/about`。变化时无需向服务器发送请求,对于只需要处理前端路由的应用程序来说,使用 Hash 模式足以满足需求。Hash 模式在传输数据量方面更小,而且兼容性最好。
在选择使用哪种模式之前,你应该考虑以下因素:
- **历史访问记录管理**:createWebHistory 可以管理浏览历史记录,使浏览器的后退/前进按钮可用,而 createWebHashHistory 不支持这些功能。
- **URL 文本可读性**:createWebHistory 生成的 URL 更具可读性,不包含任何无用信息,通常比 createWebHashHistory 生成的 URL 更优。
- **部署环境**:如果你的应用程序必须在较旧的浏览器上运行(如 IE 11 等),则应使用 createWebHashHistory。由于旧版浏览器不支持 HTML5 history API,使用 createWebHistory 可能会导致问题。
- **服务器配置**:在使用 createWebHistory 时需要确保你的服务器(例如,Apache 或 Nginx)已正确配置,以避免服务端路由失败的问题。createWebHashHistory 不需要服务器配置,因为 URL 中的哈希符号是在客户端处理的,不会向服务器发送任何请求。
因此,如果你的应用程序仅使用前端路由,无需后退/前进按钮,或者你专注于支持现代浏览器,则应使用 createWebHistory。否则,如果应用程序部署在旧的浏览器上,则应使用 createWebHashHistory。
*/
/*
RouteRecordRaw是Vue Router的一个类型定义,它用于描述路由配置的对象。它包含以下属性:
path:字符串,表示路由的路径。
name:字符串,表示路由的名称。
component:组件类型,表示路由所匹配的组件。
children:子路由配置数组,用于描述嵌套路由。
meta:对象,用于存储额外的路由元数据,例如需要验证用户权限的信息。
*/
//引入main.ts
import app from "../main";
// 引入路由模块
import noFondPage from "./not-found";
// 测试路由
import test from "./modules/test";
// pinia路由
import pinia from "./modules/pinia-store";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
redirect: "/home",
},
{
path: "/home",
name: "home",
meta: {
loading: true,
},
component: () => import("@/views/home-page/home-page.vue"),
},
{
path: "/about",
name: "about",
meta: {
loading: true,
},
component: () => import("@/views/about-page/about-page.vue"),
},
...test,
pinia,
// 404页面需要放在最后!
noFondPage,
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
// Vue3 router守卫
// 在路由跳转之前,开启loading,路由跳转之后,关闭loading
router.beforeEach((to, from, next) => {
if (to.meta.loading) {
app.config.globalProperties.$loading.showLoading();
next();
} else {
next();
}
});
router.afterEach((to) => {
if (to.meta.loading) {
app.config.globalProperties.$loading.hideLoading();
}
});
export default router;
现在主页面新建好了,需要建立分模块的页面和404页面
src\router\modules\test.ts (路由分模块测试页面)
export default [
{
path: "/test1",
name: "test1",
component: () => import("@/views/test/index-test1.vue"),
},
{
path: "/test2",
name: "test2",
component: () => import("@/views/test/index-test2.vue"),
},
{
path: "/test3",
name: "test3",
component: () => import("@/views/test/index-test3.vue"),
},
];
noFond.ts (404路由页面)
/**
* 404路由模块
*/
export default {
// 路由分模块
// 如果url找不到就会报404,必须放在路由页面最下面
path: "/:pathMatch(.*)",
component: () => import("@/views/NotFound.vue"),
};
路由结构如下图
分模块路由建立好了,也要建立对应的views页面
src\views\AboutPage\about-page.vue
<template>
<div>
<!-- about page -->
<h1>关于我们</h1>
</div>
</template>
src\views\HomePage\home-page.vue
<template>
<div>
<!-- home page -->
<h1>首页</h1>
</div>
</template>
src\views\test\index-test1.vue
<template>
<div>
<!-- test1 page -->
<h2>test1</h2>
</div>
</template>
src\views\test\index-test2.vue
<template>
<div>
<!-- test2 page -->
<h2>test2</h2>
</div>
</template>
src\views\test\index-test3.vue
<template>
<div>
<!-- test3 page -->
<h2>test3</h2>
</div>
</template>
src\views\NotFound.vue
<template>
<div>
<!-- 404 page -->
<h1>404</h1>
</div>
</template>
在main.js中引入并使用
import { createApp } from "vue";
import App from "./App.vue";
// 引入element-plus并使用中文语言包
import ElementPlus from "element-plus";
import zhCn from "element-plus/lib/locale/lang/zh-cn";
// 引入路由配置文件
import router from "./router/index";
// 引入element-plus样式
import "element-plus/dist/index.css";
const app = createApp(App);
app
.use(ElementPlus, {
locale: zhCn,
})
.use(router)
.mount("#app");
在App.vue中添加路由出口
<template>
<router-link to="/">首页 </router-link>
<router-link to="/about">关于我们 </router-link>
<router-link to="/test1">测试1 </router-link>
<router-link to="/test2">测试2 </router-link>
<router-link to="/test3">测试3 </router-link>
<el-button type="primary">按钮</el-button>
<router-view></router-view>
</template>
<script setup lang="ts">
// 占位
</script>
<style scoped lang="scss">
// 测试 styleLint
router-link {
position: fixed;
color: red;
}
</style>
—————————————— 路由分模块 完 —————————————————
以上是静态路由分模块,下面是动态路由,(根据接口动态生成,一般后台管理权限菜单使用较多)
/*
* createWebHistory 与 createWebHashHistory 的区别
* createWebHistory:使用 HTML5 History API 的路由模式。注意:这种模式要玩好,还需要后台配置支持。后台不配置你本地开发没问题,
* 一旦部署上线,刷新就会出现 404。
* createWebHashHistory:使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History API 的浏览器。
*
* 详细来说,createWebHistory 是基于 HTML5 History API 的,而 createWebHashHistory 是基于 URL 的 hash 值的。
* 在 Vue3 中,你可以通过调用 createWebHistory 或 createWebHashHistory 函数来创建路由。
*
* createWebHistory 监听浏览器的 history.pushState 和 history.replaceState 事件,并使用 HTML5 的 history API 来管理路由。
* 拥有更简单的 URL,不包含 "#" 符号。
*
* createWebHashHistory 使用浏览器的 window.location.hash 属性来管理路由。在 URL 中将使用 "#" 符号,例如:`http://localhost:300/#/about`。
* 变化时无需向服务器发送请求,对于只需要处理前端路由的应用程序来说,使用 Hash 模式足以满足需求。Hash 模式在传输数据量方面更小,而且兼容性最好。
*
* 在选择使用哪种模式之前,你应该考虑以下因素:
*
* - **历史访问记录管理**:createWebHistory 可以管理浏览历史记录,使浏览器的后退/前进按钮可用,而 createWebHashHistory 不支持这些功能。
* - **URL 文本可读性**:createWebHistory 生成的 URL 更具可读性,不包含任何无用信息,通常比 createWebHashHistory 生成的 URL 更优。
* - **部署环境**:如果你的应用程序必须在较旧的浏览器上运行(如 IE 11 等),则应使用 createWebHashHistory。
* 由于旧版浏览器不支持 HTML5 history API,使用 createWebHistory 可能会导致问题。
* - **服务器配置**:在使用 createWebHistory 时需要确保你的服务器(例如,Apache 或 Nginx)已正确配置,以避免服务端路由失败的问题。
* createWebHashHistory 不需要服务器配置,因为 URL 中的哈希符号是在客户端处理的,不会向服务器发送任何请求。
*
* 因此,如果你的应用程序仅使用前端路由,无需后退/前进按钮,或者你专注于支持现代浏览器,则应使用 createWebHistory。
* 否则,如果应用程序部署在旧的浏览器上,则应使用 createWebHashHistory。
*/
/*
RouteRecordRaw是Vue Router的一个类型定义,它用于描述路由配置的对象。它包含以下属性:
path:字符串,表示路由的路径。
name:字符串,表示路由的名称。
component:组件类型,表示路由所匹配的组件。
children:子路由配置数组,用于描述嵌套路由。
meta:对象,用于存储额外的路由元数据,例如需要验证用户权限的信息。
*/
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
//引入main.ts中的app
import app from "../main";
// pinia路由
import pinia from "./modules/pinia-store";
// 默认静态路由,不需要权限的路由
const routes: Array<RouteRecordRaw> = [
{
path: "/",
redirect: "/home",
},
{
path: "/home",
name: "home",
meta: {
loading: true,
},
component: () => import("@/views/home-page/home-page.vue"),
},
{
path: "/about",
name: "about",
meta: {
loading: true,
},
component: () => import("@/views/about-page/about-page.vue"),
},
pinia,
{
// vue-router4动态加载的模式下,当我们在当前页面刷新浏览器时,会出现一个警告
// [Vue Router warn]: No match found for location with path
// 解决方法: 在路由配置中添加一个通配符的路由,用来匹配所有的路由地址 404
// 如果url找不到就会报404,必须放在路由页面最下面
path: "/:catchAll(.*)",
component: () => import("@/views/errors-view/not-found.vue"),
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
let isRoutesGenerated = false; // 添加一个标志位,用来判断是否已经生成了动态路由
router.beforeEach((to, from, next) => {
if (to.meta.loading) app.config.globalProperties.$Loading.showLoading();
//先执行的是 isRoutesGenerated,然后再取反。
if (!isRoutesGenerated) {
// 判断是否已经生成了动态路由
try {
// 从后台获取菜单 axios.get('/api/menu')
const menu: Array<RouteRecordRaw> = [
{
path: "/test1",
name: "test1",
meta: {
loading: true,
},
component: () => import("@/views/dynamic-routing/index-test1.vue"),
},
{
path: "/test2",
name: "test2",
meta: {
loading: true,
},
component: () => import("@/views/dynamic-routing/index-test2.vue"),
},
{
path: "/test3",
name: "test3",
meta: {
loading: true,
},
component: () => import("@/views/dynamic-routing/index-test3.vue"),
},
];
// 生成动态路由
generateRoutes(menu);
isRoutesGenerated = true; // 设置标志位为true,表示已经生成了动态路由
// 添加404页面
// router.addRoute(notFoundPage);
// 重新跳转到目标路由
next({ ...to, replace: true });
} catch (error) {
console.error("无法获取菜单数据:", error);
}
} else {
next();
}
});
// 根据菜单数据动态生成路由
function generateRoutes(menu: any) {
for (let i = 0; i < menu.length; i++) {
const item = menu[i];
const route: any = {
path: item.path,
name: item.name,
meta: {
loading: item.meta.loading,
},
component: item.component,
};
// 递归生成子路由
if (item.children && item.children.length > 0) {
route.children = generateRoutes(item.children);
}
// 追加在404页面前面
routes.splice(routes.length - 1, 0, route);
// 在路由中添加新路由
router.addRoute(route);
}
// console.log("!这里输出 🚀 ==>:", routes);
}
router.afterEach((to) => {
if (to.meta.loading) app.config.globalProperties.$Loading.hideLoading();
});
export default router;
10 配置husky
前言
虽然上面已经配置好了**
eslint
、preitter
与stylelint
**,但是还是存在以下问题。对于不使用**
vscode
的,或者没有安装eslint
、preitter
与stylelint
**插件的同学来说,就不能实现在保存的时候自动的去修复与和格式化代码。这样提交到**
git
仓库的代码还是不符合要求的。因此需要引入强制的手段来保证提交到git
**仓库的代码时符合我们的要求的。
husky
是一个用来管理git hook
的工具,git hook
即在我们使用git
提交代码的过程中会触发的钩子。
husky
在介绍
husky
之前,首先要理解什么是 hook(钩子),在前端 Vue 框架中提供了 beforCreated、created、beforeMounted、mounted 等函数、这些函数都是钩子,也常被称为‘生命周期钩子函数’,它们会在 Vue 实例化过程中有序地执行。在 Git 中也存在一些钩子,其中较常用的有
pre-push、pre-commit
、commit-msg,其中pre-commit
钩子会在 commit 前触发,pre-push
会在 push 前触发。(提示:所有钩子默认情况下是禁用的)这些钩子可以用来干嘛?
比方我们可以利用
pre-commit
钩子在 commit 时对代码先进行 eslint 检查,如果不合格就不给 commit,
不过使用 git 钩子稍微麻烦,于是就有了husky
,它能让我们使用 git 钩子变得更加容易。
安装依赖:
首先确保你的根目录下有.git文件
pnpm add husky -D
npx husky install
npx husky add .husky/pre-commit
npx husky add .husky/commit-msg
会在项目根目录出现,以上命令将会下载初始化 husky 并新增 pre-commit
钩子文件
下面只需在 pre-commit
文件自定义命令,即可在 commit 前触发,就这样,是不是很简单?
比如:
**pre-commit
**内容如下
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
echo "温馨提醒:请您确保代码已经格式化并且通过了lint检查(npm run CheckAll)在提交代码奥~!"
下面 commit 时将会提前输出 “echo‘’后的话,如图
这么你就知道了,原来"git 也有生命周期钩子",你就可以利用 prettier
工具可以在代码保存时进行格式化与检查
你可以在 commit 前让 pre-commit 执行 prettier 来检查代码格式是否合格,合格了才给 commit。
pre-commit
内容 修改 如下
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
echo "温馨提醒:请您确保代码已经格式化并且通过了lint检查(npm run CheckAll)在提交代码奥~!"
# CheckAll 是你的格式化脚本,在上面我已经将ESlint:check、Prettier:check、StyleLint:check整合为一个命令 CheckAll 了!!!
npm run CheckAll
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
echo "💝💝温馨提醒🎉非错误💀:请您确保代码无误在提交代码奥~!"
git 运行如下:
升级npm后
注意:虽然格式化了,但 helloWord.vue 文件状态并没有一起 commit 上去,仍然处于修改状态,如图:
也就是说 prettier 修复后,我们 还要手动再次 git add . & git commit 上去,
修复后,再次提交代码,想要不额外提交commit记录,可以使用如下命令
git commit --amend 是用来修改上一次提交的 commit message 的命令,
git commit --no-edit 表示不修改 commit message,直接使用原来的提交信息。他们一起使用就是修改上一次提交的 commit message,不额外提交commit记录
git commit --amend --no-edit
—————————————— 配置husky 完 —————————————————
10.1 配置git规范 (npm 脚本运行commit)
1 .首先,在项目中安装commitizen和cz-conventional-changelog:
pnpm install cz-conventional-changelog
2 .然后,在package.json文件中添加以下配置:
{
"scripts": {
"commit": "git pull && git add -A && git-cz && git push"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}
其中,"scripts"中的"commit"表示以后提交代码时使用的命令,"config"中的"commitizen"表示指定使用哪个规范,这里我们使用的是cz-conventional-changelog。
3 .最后,执行以下命令使配置生效:
npm run commit
这时就可以看到git-cz的交互界面了,按照要求填写完整个提交信息即可提交代码。
10.2 保证提交合规:(非 git cz 命令时启动 commit-msg 校验规则)
- 确保你已经全局安装了**
commitizen
和cz-conventional-changelog
**两个包。如果没有安装,可以使用以下命令进行安装:
pnpm install -g commitizen cz-conventional-changelog
2.创建commit-msg
#!/bin/sh
commit_msg_file=$1
# 检查提交信息是否以指定的单词开头,并且描述不超过50个字符
if ! grep -qE "^(revert|feat|fix|docs|style|refactor|perf|test|chore|build|ci)\(.+\):[[:space:]].{1,50}$" "$commit_msg_file"; then
echo "❌:提交信息格式不符合规范,❗💢💢请参考以下格式:(或者您可以使用git cz命令,也可预览项目中的GIT-README.md文档 奥~)📖🎉🎉"
echo "类型范围(必填): 描述(不超过50个字符)"
exit 1
fi
exit 0
完整的 script 脚本配置:
使用 concurrently
同时运行多个命令
pnpm install -g concurrently
"scripts": {
"pnpm:": "pnpm install",
"dev": "concurrently \"vite --open\" \"npx husky install\"",
"build": "\"npm run CheckAll\" && vite build",
"CheckAll": "concurrently \"npm run ESlint:check\" \"npm run Prettier:check\" \"npm run StyleLint:check\"",
"ESlint:check": "eslint . --ext .vue,.js,cjs,mjs,.ts,.cts,.mts,.jsx,.tsx --fix",
"Prettier:check": "prettier --write \"./**/*.{html,vue,ts,cts,mts,js,mjs,cjs,json,md,scss}\"",
"StyleLint:check": "stylelint \"./**/*.{css,scss,less}\" --fix",
"commit": "git pull && git add -A && git-cz && git push"
},
此时不管是git commit -m ‘xxx’ 还是 git cz 都需要满足规则才可提交!
到这里已经集成了
eslint 、prettier、stylelint、husky、commitizen、sass 项目地址下载
—————————————— 配置git规范 完 —————————————————
11 配置环境变量
1:输入pnpm path 找到pnpm路径
或者手动查找
2:右键我的电脑、属性、高级系统设置、环境变量、然后配置
需要配置两个
打包优化看这里: 你把 vite打包 玩明白_0.活在风浪里的博客-CSDN博客
—————————————— 环境变量 完 —————————————————
12 配置pinia
13 集成Axios
Vue3 + Ts + Vite 封装一套企业级axiso全流程_彩色之外的博客-CSDN博客
14 封装全局 MyLoading
目录结构
index.ts
import { App, createApp } from "vue";
import Loading from "./loading.vue";
export default {
loading: null as any,
install(app: App) {
if (this.loading) {
app.config.globalProperties.$Loading = this.loading;
return;
}
const instance = createApp(Loading);
const div = document.createElement("div");
const body = document.body;
body.appendChild(div);
this.loading = instance.mount(div);
app.config.globalProperties.$Loading = {
showLoading: this.loading.showLoading,
hideLoading: this.loading.hideLoading,
};
},
};
loading.vue
<template>
<div id="loading" v-show="visible">
<div class="loader-overlay"></div>
<div class="loader-cogs">
<div class="loader-cogs-top">
<div class="top-part"></div>
<div class="top-part"></div>
<div class="top-part"></div>
<div class="top-hole"></div>
</div>
<div class="loader-cogs-left">
<div class="left-part"></div>
<div class="left-part"></div>
<div class="left-part"></div>
<div class="left-hole"></div>
</div>
<div class="loader-cogs-bottom">
<div class="bottom-part"></div>
<div class="bottom-part"></div>
<div class="bottom-part"></div>
<div class="bottom-hole"></div>
</div>
<p>Loading ...</p>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
// eslint-disable-next-line vue/multi-word-component-names
name: "Loading",
setup() {
// 控制组件的状态
let visible = ref(false);
// 显示组件
let showLoading = () => {
visible.value = true;
};
// 隐藏组件
let hideLoading = () => {
visible.value = false;
};
return { visible, showLoading, hideLoading };
},
});
</script>
<style lang="scss" scoped>
#loading {
position: absolute;
inset: 0;
z-index: 9999;
margin: auto;
.loader-overlay {
position: absolute;
inset: 0;
z-index: -1;
margin: auto;
width: 150px;
height: 150px;
background: transparent;
border-radius: 100%;
box-shadow: 0 0 0 100vw rgb(255 255 255 / 67%),
0 0 19px 0 rgb(0 0 0 / 16%) inset;
}
.loader-cogs {
position: absolute;
top: -120px !important;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: -2;
margin: auto;
width: 100px;
height: 100px;
}
.loader-cogs-top {
position: relative;
width: 100px;
height: 100px;
transform-origin: 50px 50px;
animation: rotate 10s infinite linear;
}
.loader-cogs-top div:nth-of-type(1) {
transform: rotate(30deg);
}
.loader-cogs-top div:nth-of-type(2) {
transform: rotate(60deg);
}
.loader-cogs-top div:nth-of-type(3) {
transform: rotate(90deg);
}
.loader-cogs-top div.top-part {
position: absolute;
width: 100px;
height: 100px;
background: #f98db9;
border-radius: 10px;
}
.loader-cogs-top div.top-hole {
position: absolute;
inset: 0;
margin: auto;
width: 50px;
height: 50px;
background: white;
border-radius: 100%;
}
.loader-cogs-left {
position: relative;
top: 28px;
left: -24px;
width: 80px;
height: 80px;
transform: rotate(16deg);
transform-origin: 40px 40px;
animation: rotate-left 10s 0.1s infinite reverse linear;
}
.loader-cogs-left div:nth-of-type(1) {
transform: rotate(30deg);
}
.loader-cogs-left div:nth-of-type(2) {
transform: rotate(60deg);
}
.loader-cogs-left div:nth-of-type(3) {
transform: rotate(90deg);
}
.loader-cogs-left div.left-part {
position: absolute;
width: 80px;
height: 80px;
background: #97ddff;
border-radius: 6px;
}
.loader-cogs-left div.left-hole {
position: absolute;
inset: 0;
margin: auto;
width: 40px;
height: 40px;
background: white;
border-radius: 100%;
}
.loader-cogs-bottom {
position: relative;
top: -65px;
left: 79px;
width: 60px;
height: 60px;
transform-origin: 30px 30px;
animation: rotate-left 10.2s 0.4s infinite linear;
transform: rotate(4deg);
}
.loader-cogs-bottom div:nth-of-type(1) {
transform: rotate(30deg);
}
.loader-cogs-bottom div:nth-of-type(2) {
transform: rotate(60deg);
}
.loader-cogs-bottom div:nth-of-type(3) {
transform: rotate(90deg);
}
.loader-cogs-bottom div.bottom-part {
position: absolute;
width: 60px;
height: 60px;
background: #ffcd66;
border-radius: 5px;
}
.loader-cogs-bottom div.bottom-hole {
position: absolute;
inset: 0;
margin: auto;
width: 30px;
height: 30px;
background: white;
border-radius: 100%;
}
/* Animations */
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes rotate-left {
from {
transform: rotate(16deg);
}
to {
transform: rotate(376deg);
}
}
@keyframes rotate-left {
from {
transform: rotate(16deg);
}
to {
transform: rotate(376deg);
}
}
@keyframes rotate-right {
from {
transform: rotate(4deg);
}
to {
transform: rotate(364deg);
}
}
@keyframes rotate-right {
from {
transform: rotate(4deg);
}
to {
transform: rotate(364deg);
}
}
}
</style>
main.ts
引入loading并导出app用于在路由拦截时候调用
router/index.ts
/* 静态路由 :不需要权限的路由
* createWebHistory 与 createWebHashHistory 的区别
* createWebHistory:使用 HTML5 History API 的路由模式。注意:这种模式要玩好,还需要后台配置支持。后台不配置你本地开发没问题,
* 一旦部署上线,刷新就会出现 404。
* createWebHashHistory:使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History API 的浏览器。
*
* 详细来说,createWebHistory 是基于 HTML5 History API 的,而 createWebHashHistory 是基于 URL 的 hash 值的。
* 在 Vue3 中,你可以通过调用 createWebHistory 或 createWebHashHistory 函数来创建路由。
*
* createWebHistory 监听浏览器的 history.pushState 和 history.replaceState 事件,并使用 HTML5 的 history API 来管理路由。
* 拥有更简单的 URL,不包含 "#" 符号。
*
* createWebHashHistory 使用浏览器的 window.location.hash 属性来管理路由。在 URL 中将使用 "#" 符号,例如:`http://localhost:300/#/about`。
* 变化时无需向服务器发送请求,对于只需要处理前端路由的应用程序来说,使用 Hash 模式足以满足需求。Hash 模式在传输数据量方面更小,而且兼容性最好。
*
* 在选择使用哪种模式之前,你应该考虑以下因素:
*
* - **历史访问记录管理**:createWebHistory 可以管理浏览历史记录,使浏览器的后退/前进按钮可用,而 createWebHashHistory 不支持这些功能。
* - **URL 文本可读性**:createWebHistory 生成的 URL 更具可读性,不包含任何无用信息,通常比 createWebHashHistory 生成的 URL 更优。
* - **部署环境**:如果你的应用程序必须在较旧的浏览器上运行(如 IE 11 等),则应使用 createWebHashHistory。
* 由于旧版浏览器不支持 HTML5 history API,使用 createWebHistory 可能会导致问题。
* - **服务器配置**:在使用 createWebHistory 时需要确保你的服务器(例如,Apache 或 Nginx)已正确配置,以避免服务端路由失败的问题。
* createWebHashHistory 不需要服务器配置,因为 URL 中的哈希符号是在客户端处理的,不会向服务器发送任何请求。
*
* 因此,如果你的应用程序仅使用前端路由,无需后退/前进按钮,或者你专注于支持现代浏览器,则应使用 createWebHistory。
* 否则,如果应用程序部署在旧的浏览器上,则应使用 createWebHashHistory。
*/
/*
RouteRecordRaw是Vue Router的一个类型定义,它用于描述路由配置的对象。它包含以下属性:
path:字符串,表示路由的路径。
name:字符串,表示路由的名称。
component:组件类型,表示路由所匹配的组件。
children:子路由配置数组,用于描述嵌套路由。
meta:对象,用于存储额外的路由元数据,例如需要验证用户权限的信息。
*/
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
// pinia路由
import pinia from "./modules/pinia-store";
// 默认静态路由,不需要权限的路由
export const routes: Array<RouteRecordRaw> = [
{
path: "/",
redirect: "/home",
},
{
path: "/home",
name: "home",
meta: {
loading: true,
},
component: () => import("@/views/home-page/home-page.vue"),
},
{
path: "/about",
name: "about",
meta: {
loading: true,
},
component: () => import("@/views/about-page/about-page.vue"),
},
pinia,
{
// vue-router4动态加载的模式下,当我们在当前页面刷新浏览器时,会出现一个警告
// [Vue Router warn]: No match found for location with path
// 解决方法: 在路由配置中添加一个通配符的路由,用来匹配所有的路由地址 404
// 如果url找不到就会报404,必须放在路由页面最下面
path: "/:catchAll(.*)",
component: () => import("@/views/errors-view/not-found.vue"),
},
];
export const router = createRouter({
history: createWebHashHistory(),
routes,
});
// 路由守卫 用来动态生成路由
import { router, routes } from "./index";
import { RouteRecordRaw } from "vue-router";
//引入main.ts中的app
import app from "../main";
let isRoutesGenerated = false; // 添加一个标志位,用来判断是否已经生成了动态路由
router.beforeEach((to, from, next) => {
if (to.meta.loading) app.config.globalProperties.$Loading.showLoading();
//先执行的是 isRoutesGenerated,然后再取反。
if (!isRoutesGenerated) {
// 判断是否已经生成了动态路由
try {
// 从后台获取菜单 axios.get('/api/menu')
const menu: Array<RouteRecordRaw> = [
{
path: "/test1",
name: "test1",
meta: {
loading: true,
},
component: () => import("@/views/dynamic-routing/index-test1.vue"),
},
{
### 总结
面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。
还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。
万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。
**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**
**前端面试题汇总**
![](https://img-blog.csdnimg.cn/img_convert/42728594459506983a38ca2b86545fc6.png)
**JavaScript**
![](https://img-blog.csdnimg.cn/img_convert/7796de226b373d068d8f5bef31e668ce.png)
**前端资料汇总**
![](https://img-blog.csdnimg.cn/img_convert/6e0ba223f65e063db5b1b4b6aa26129a.png)