B01-基于VITE开发框架
🧑🎓 个人主页:Silence Lamb
📖 本章内容:【基于VITE项目开发框架】
Silence-Vue v1.0.0
基于VITE项目开发框架
vite + vue3 + vueUse+ ts + pinia
一、基础配置
1.1🌳【基本环境】
💡
Tips
:vue-cli + vue3 + yarn + vite + ts
# vue/cli
yarn global add @vue/cli
# typescript parser
yarn add @typescript-eslint/parser --dev
# 如果没有安装yarn,需先安装yarn
npm i yarn -g
# 创建vite项目
yarn create vite vue3-vite-app --template vue-ts
# 进入项目根目录
cd vue3-vite-app
# 安装依赖
yarn install
# 启动项目
yarn dev
- 推荐使用新一代 pnpm 包管理工具,性能和速度以及 node_modules依赖管理都很优秀
💡
Tips
:建议配合 .npmrc 配置使用
# 提升一些依赖包至 node_modules
# 解决部分包模块not found的问题
# 用于配合 pnpm
shamefully-hoist = true# node-sass 下载问题
# sass_binary_site="https://npm.taobao.org/mirrors/node-sass/"
💡Tips
:模板语法配合jsx语法,使用起来非常方便、灵活~一些必须的插件
yarn add @vitejs/plugin-legacy -S // 低版本浏览器兼容
yarn add @vitejs/plugin-vue -S // vue 支持
yarn add @vitejs/plugin-vue-jsx -S // jsx 支持
1.2🌳【代码风格】
💡
Tips
:开启Eslint的支持
yarn add eslint -D
yarn add eslint-plugin-vue -D
yarn add @typescript-eslint/eslint-plugin -D
yarn add @typescript-eslint/parser -D
💡
Tips
:echo {}> .eslintrc.js
module.exports = {root: true,env: {browser: true,node: true,es6: true,},parser: 'vue-eslint-parser',parserOptions: {parser: '@typescript-eslint/parser',ecmaVersion: 2020,sourceType: 'module',jsxPragma: 'React',ecmaFeatures: {jsx: true,},},plugins: ['vue', '@typescript-eslint', 'prettier'],rules: {'@typescript-eslint/ban-ts-ignore': 'off','@typescript-eslint/no-unused-vars': 'off','@typescript-eslint/explicit-function-return-type': 'off','@typescript-eslint/no-explicit-any': 'off','@typescript-eslint/no-var-requires': 'off','@typescript-eslint/no-empty-function': 'off','@typescript-eslint/no-use-before-define': 'off','@typescript-eslint/ban-ts-comment': 'off','@typescript-eslint/ban-types': 'off','@typescript-eslint/no-non-null-assertion': 'off','@typescript-eslint/explicit-module-boundary-types': 'off','no-var': 'error','prettier/prettier': ['error', { trailingComma: 'es5', singleQuote: true, printWidth: 80, tabWidth: 2 }],// 禁止出现console'no-console': 'warn',// 禁用debugger'no-debugger': 'warn',// 禁止出现重复的 case 标签'no-duplicate-case': 'warn',// 禁止出现空语句块'no-empty': 'warn',// 禁止不必要的括号'no-extra-parens': 'off',// 禁止对 function 声明重新赋值'no-func-assign': 'warn',// 禁止在 return、throw、continue 和 break 语句之后出现不可达代码'no-unreachable': 'warn',// 强制所有控制语句使用一致的括号风格curly: 'warn',// 要求 switch 语句中有 default 分支'default-case': 'warn',// 强制尽可能地使用点号'dot-notation': 'warn',// 要求使用 === 和 !==eqeqeq: 'warn',// 禁止 if 语句中 return 语句之后有 else 块'no-else-return': 'warn',// 禁止出现空函数'no-empty-function': 'warn',// 禁用不必要的嵌套块'no-lone-blocks': 'warn',// 禁止使用多个空格'no-multi-spaces': 'warn',// 禁止多次声明同一变量'no-redeclare': 'warn',// 禁止在 return 语句中使用赋值语句'no-return-assign': 'warn',// 禁用不必要的 return await'no-return-await': 'warn',// 禁止自我赋值'no-self-assign': 'warn',// 禁止自身比较'no-self-compare': 'warn',// 禁止不必要的 catch 子句'no-useless-catch': 'warn',// 禁止多余的 return 语句'no-useless-return': 'warn',// 禁止变量声明与外层作用域的变量同名'no-shadow': 'off',// 允许delete变量'no-delete-var': 'off',// 强制数组方括号中使用一致的空格'array-bracket-spacing': 'warn',// 强制在代码块中使用一致的大括号风格'brace-style': 'warn',// 强制使用骆驼拼写法命名约定camelcase: 'warn',// 强制使用一致的缩进indent: 'off',// 强制在 JSX 属性中一致地使用双引号或单引号// 'jsx-quotes': 'warn',// 强制可嵌套的块的最大深度4'max-depth': 'warn',// 强制最大行数 300// "max-lines": ["warn", { "max": 1200 }],// 强制函数最大代码行数 50// 'max-lines-per-function': ['warn', { max: 70 }],// 强制函数块最多允许的的语句数量20'max-statements': ['warn', 100],// 强制回调函数最大嵌套深度'max-nested-callbacks': ['warn', 3],// 强制函数定义中最多允许的参数数量'max-params': ['warn', 3],// 强制每一行中所允许的最大语句数量'max-statements-per-line': ['warn', { max: 1 }],// 要求方法链中每个调用都有一个换行符'newline-per-chained-call': ['warn', { ignoreChainWithDepth: 3 }],// 禁止 if 作为唯一的语句出现在 else 语句中'no-lonely-if': 'warn',// 禁止空格和 tab 的混合缩进'no-mixed-spaces-and-tabs': 'warn',// 禁止出现多行空行'no-multiple-empty-lines': 'warn',// 禁止出现;semi: ['warn', 'never'],// 强制在块之前使用一致的空格'space-before-blocks': 'warn',// 强制在 function的左括号之前使用一致的空格// 'space-before-function-paren': ['warn', 'never'],// 强制在圆括号内使用一致的空格'space-in-parens': 'warn',// 要求操作符周围有空格'space-infix-ops': 'warn',// 强制在一元操作符前后使用一致的空格'space-unary-ops': 'warn',// 强制在注释中 // 或 /* 使用一致的空格// "spaced-comment": "warn",// 强制在 switch 的冒号左右有空格'switch-colon-spacing': 'warn',// 强制箭头函数的箭头前后使用一致的空格'arrow-spacing': 'warn','no-var': 'warn','prefer-const': 'warn','prefer-rest-params': 'warn','no-useless-escape': 'warn','no-irregular-whitespace': 'warn','no-prototype-builtins': 'warn','no-fallthrough': 'warn','no-extra-boolean-cast': 'warn','no-case-declarations': 'warn','no-async-promise-executor': 'warn',},
}
💡
Tips
:echo {}> .eslintignore
# eslint 忽略检查 (根据项目需要自行添加)
node_modules
dist
1.3🌳【代码格式】
💡
Tips
:安装 prettier
# 安装 prettier
yarn add prettier -D
# 安装插件 eslint-config-prettier
yarn add eslint-config-prettier -D
💡
Tips
:echo {}> .prettierrc.js
/ @Description :* @Author : SilenceLamb* @Version : V1.0.0*/
module.exports = {printWidth: 150,// 指定每个缩进级别的空格数tabWidth: 4,// 使用制表符而不是空格缩进行useTabs: false,// 在语句末尾打印分号semi: false,// 使用单引号而不是双引号singleQuote: true,// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"quoteProps: 'as-needed',// 在JSX中使用单引号而不是双引号jsxSingleQuote: false,// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认nonetrailingComma: 'es5',// 在对象文字中的括号之间打印空格bracketSpacing: true,// 在单独的箭头函数参数周围包括括号 always:(x) => x \\ avoid:x => xarrowParens: 'always',// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码rangeStart: 0,rangeEnd: Infinity,// 指定要使用的解析器,不需要写文件开头的 @prettierrequirePragma: false,// 不需要自动在文件开头插入 @prettierinsertPragma: false,// 使用默认的折行标准 always\\never\\preserveproseWrap: 'preserve',// 指定HTML文件的全局空格敏感度 css\\strict\\ignorehtmlWhitespaceSensitivity: 'css',// Vue文件脚本和样式标签缩进vueIndentScriptAndStyle: false,// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"endOfLine: 'lf',
}
💡
Tips
:echo {}> .prettierignore
# 忽略格式化文件 (根据项目需要自行添加)
node_modules
dist
💡
Tips
:package.json
"scripts": {"lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx","prettier": "prettier --write ."
},
1.4🌳【环境变量 】
import.meta.env.MODE: {string} 应用运行的模式
import.meta.env.BASE_URL: {string} 部署应用时的基本 URL。他由base 配置项决定
import.meta.env.PROD: {boolean} 应用是否运行在生产环境
import.meta.env.DEV: {boolean} 应用是否运行在开发环境 (永远与 import.meta.env.PROD相反)
import.meta.env.SSR: {boolean} 应用是否运行在 server 上
- 🌳 .env 文件
.env # 所有情况下都会加载
.env.local # 所有情况下都会加载,但会被 git 忽略
.env.[mode] # 只在指定模式下加载
.env.[mode].local # 只在指定模式下加载,但会被 git 忽略
- 🌳在tsconfig.json中添加"types": [ “vite/client” ]
- 用来提供import.meta.env 上 Vite 注入的环境变量的类型定义
"compilerOptions": {"types": [ "vite/client" ]
}
💡
Tips
:另外一种配置方式
- 🌳 开发环境:.env.development
# 页面标题
VITE_APP_TITLE=开发环境
# 开发环境配置
VITE_NODE_ENV='development'
# 开发环境
VITE_APP_BASE_API='/dev-api'
# 路由懒加载
VITE_CLI_BABEL_TRANSPILE_MODULES=true
- 🌳 线上环境:.env.production
# 页面标题
VITE_APP_TITLE=线上环境
# 生产环境配置
VITE_NODE_ENV='production'
# 生产环境
VITE_APP_BASE_API='/prod-api'
# 路由懒加载
VITE_CLI_BABEL_TRANSPILE_MODULES=true
- 🌳 测试环境配置:.env.production
# 页面标题
VITE_APP_TITLE=测试环境配置
VITE_NODE_ENV=production
# 测试环境配置
VITE_NODE_ENV='staging'
# 测试环境
VITE_APP_BASE_API='/stage-api'
- 🌳为环境变量增加智能提示:src/env.d.ts
/* @Description : 为环境变量增加智能提示* @Author : SilenceLamb* @Version : V1.0.0*/
interface ImportMetaEnv {//定义提示信息 数据是只读的无法被修改readonly VITE_APP_TITLE: string;readonly VITE_NODE_ENV: string;readonly VITE_APP_BASE_API: string;//多个变量定义多个...
}
interface ImportMeta {readonly env: ImportMetaEnv;
}
💡
Tips
:vite.config.ts: 不能使用 import.meta.env
const ViteEnv= loadEnv(mode, process.cwd());console.log(ViteEnv.VITE_APP_BASE_API);console.log(ViteEnv.VITE_NODE_ENV);
💡
Tips
:package.json
"scripts": {"dev": "vite --mode development","build": "vue-tsc --noEmit && vite build","build:prod": "vue-tsc --noEmit && vite build --mode production",},
- 🌳为环境变量增加智能提示:src/env.d.ts
/ @Description : Vite相关方法* @Author : SilenceLamb* @Version : V1.0.0*/
export default {/* @Description : 是否是开发环境* @param mode 模式*/isDev(mode: string): boolean {return mode === 'development';},/* @Description : 是否是线上环境* @param mode 模式*/isProd(mode: string | undefined): boolean {return mode === 'production';},/* @Description : 是否是构建* @param common 启动环境*/isBuild(common: string | undefined): boolean {return common === 'build';},/* @Description : 是否是启动服务器* @param common 启动环境*/isServer(common: string | undefined): boolean {return common === 'server';},
};
1.4🌳【别名配置】
- 例如,如果我们要导入src下的components目录中的组件,则可以这样写
import MyComponent from '@/components/MyComponent';
💡
Tips
:首先,在tsconfig.json中添加一个"paths"属性,然后以别名为键名,路径为键值
- 🌳别名配置:tsconfig.json
{"compilerOptions": {"baseUrl": "./","paths": {"/@/*": ["src/*"]}},"exclude": ["node_modules", "dist"]
}
💡
Tips
:在项目根目录下新建config/aliases文件夹,在其中新建一个index文件
- 🌳别名配置:config/aliases/index.ts
/ @Description : 配置别名* @Author : SilenceLamb* @Version : V1.0.0*/
import { resolve } from 'path';function pathResolve(dir: string) {return resolve(process.cwd(), '.', dir);
}
export default {// 导入时想要省略的扩展名列表extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],alias: {'/@': pathResolve('src'),'/#': pathResolve('config'),},
};
💡
Tips
: 如果path找不到:npm install @types/node --save-dev
- 🌳 Vite配置:config/index.ts
/ @Description : Vite配置* @Author : SilenceLamb* @Version : V1.0.0*/
import buildConfig from '/#/build';
import createVitePlugins from '/#/plugins';
import resolve from '/#/aliases';
import serverConfig from '/#/server';export default function viteConfig(isBuild: boolean, VITE_APP_BASE_API: string) {const viteConfig = {['resolve']: resolve,};return viteConfig;
}
- 🌳 vite.config.ts配置:vite.config.ts
import { UserConfig, ConfigEnv, loadEnv } from 'vite';
import viteUtils from '/@/utils/modules/vite';
import viteConfig from './config';
export default ({ command, mode }: ConfigEnv): UserConfig => {const env = loadEnv(mode, process.cwd());const { VITE_APP_BASE_API } = env;const isBuild = viteUtils.isBuild(command);//vite配置在config文件下console.log(VITE_APP_BASE_API)const ViteConfig = viteConfig(isBuild, VITE_APP_BASE_API);return {//静态资源publicDir: 'public',//项目基础路径base: viteUtils.isDev(mode) ? '/' : '',//build时 去除 console和 debuggeresbuild: {drop: ['console', 'debugger'],},resolve: ViteConfig['resolve'],};
};
二、跨域配置
2.1🍖【通用常量】
- 🍖 项目设置:config/constant/silence.ts
/ @Description : 项目设置* @Author : SilenceLamb* @Version : V1.0.0*/
export default {/* 前端端口*/vuePort: 80,/* 后端接口*/adminUrl: 'http://localhost:88',
};
- 🍖 通用设置:config/constant/modules/common.ts
/ @Description : 通用设置* @Author : SilenceLamb* @Version : V1.0.0*/
export default {/* @Description : 在npm run build 或 yarn build 时 ,生成文件的目录名称* @param {String} param -(默认dist)*/outputDir: 'dist',/* @Description : 用于放置生成的静态资源 (js、css、img、fonts) 的* @param {String} param -(默认static)*/assetsDir: 'static',
};
- 🍖 整合常量:config/constant/modules/common.ts
/ @Description : 整合常量* @Author : SilenceLamb* @Version : V1.0.0*/
import silence from './modules/silence';
import common from './modules/common';const constants = {['silence']: silence,['common']: common,
};
export default constants;
2.2🍖【跨域配置】
- 🍖 代理目标列表:config/server/modules/index.ts
/ @Description : 代理目标列表* @Author : SilenceLamb* @Version : V1.0.0*/
import { ProxyOptions } from 'vite';
import constants from '/#/constant';
type ProxyTargetList = Record<string, ProxyOptions>;
export default function getProxy(VITE_APP_PROD_API) {const proxy: ProxyTargetList = {[VITE_APP_PROD_API]: {target: constants['silence'].adminUrl,changeOrigin: true,rewrite: (path) => path.replace(new RegExp(`^${VITE_APP_PROD_API}`), ''),},};return proxy;
}
- 🍖 跨域配置:config/server/index.ts
/ @Description : 跨域配置* @Author : SilenceLamb* @Version : V1.0.0*/
import getProxy from '/#/server/modules';
import constants from '/#/constant';export default function serverConfig(VITE_APP_PROD_API: string) {return {hmr: { overlay: false }, // 禁用或配置 HMR 连接 设置 server.hmr.overlay 为 false 可以禁用服务器错误遮罩层// 服务配置open: true, // 类型: boolean | string在服务器启动时自动在浏览器中打开应用程序;cors: false, // 类型: boolean | CorsOptions 为开发服务器配置 CORS。默认启用并允许任何源host: '0.0.0.0', // IP配置,支持从IP启动port: constants['silence'].vuePort,proxy: getProxy(VITE_APP_PROD_API),};
}
- 🍖 Vite配置:config/index.ts
/ @Description : Vite配置* @Author : SilenceLamb* @Version : V1.0.0*/
import resolve from '/#/aliases';
import serverConfig from '/#/server';export default function viteConfig(isBuild: boolean, VITE_APP_BASE_API: string) {return {['resolve']: resolve,['server']: serverConfig(VITE_APP_BASE_API),};
}
- 🍖 vite.config.ts配置:vite.config.ts
import { UserConfig, ConfigEnv, loadEnv } from 'vite';
import viteUtils from '/@/utils/modules/vite';
import viteConfig from './config';
export default ({ command, mode }: ConfigEnv): UserConfig => {const { VITE_APP_BASE_API } = loadEnv(mode, process.cwd());//是否是build命令const isBuild = viteUtils.isBuild(command);//vite配置在config文件下const ViteConfig = viteConfig(isBuild, VITE_APP_BASE_API);return {//静态资源publicDir: 'public',//项目基础路径base: viteUtils.isDev(mode) ? '/' : '',//build时 去除 console和 debuggeresbuild: {drop: ['console', 'debugger'],},resolve: ViteConfig['resolve'],//跨域配置server: ViteConfig['server'],};
};
三、按需加载
- 🍁首先你需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件
yarn add unplugin-vue-components unplugin-auto-import -D
3.1🍁【自动引入依赖】
- 🍁自动引入依赖:config/plugins/modules/importDeps.ts
/ @Description : 按需加载,自动引入依赖* @Author : SilenceLamb* @Version : V1.0.0*/
import AutoImport from 'unplugin-auto-import/vite'
components/resolvers'
export default function ImportDeps() {return AutoImport({imports: ['vue', 'vue-router', 'vue-i18n', '@vueuse/head', '@vueuse/core'],dts: 'src/auto/auto-imports.d.ts',})
}
3.2🍁【自动引入组件】
- 🍁自动引入依赖:config/plugins/modules/importComp.ts
/ @Description : 按需加载,自动引入组件* @Author : SilenceLamb* @Version : V1.0.0*/
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';export default function ImportComp() {return Components({// 自动导入自定义的组件dirs: ['src/components'],// 组件的有效文件扩展名extensions: ['vue'],// 搜索子目录deep: true,// 组件解释器resolvers: [AntDesignVueResolver({ importStyle: 'less' })],// 文件生成目录dts: 'src/auto/components.d.ts',// 允许子目录作为组件的命名空间前缀directoryAsNamespace: false,// 用于忽略命名空间前缀的子目录路径// works when `directoryAsNamespace: true`globalNamespaces: [],// 用于转换目标的筛选器include: [/\\.vue$/, /\\.vue\\?vue/],exclude: [/[\\\\/]node_modules[\\\\/]/, /[\\\\/]\\.git[\\\\/]/, /[\\\\/]\\.nuxt[\\\\/]/],});
}
3.3🍁【自动引入样式】
- 当你使用unplugin-vue-components来引入ui库的时候,message, notification,toast 等引入样式不生效
💡
Tips
:安装vite-plugin-style-import,实现message, notification,toast 等引入样式自动引入
yarn add vite-plugin-style-import -D
yarn add consola -D
- 🍁自动引入样式:config/plugins/modules/importStyle.ts
/ @Description : 自动引入样式* @Author : SilenceLamb* @Version : V1.0.0*/
import { createStyleImportPlugin, AndDesignVueResolve } from 'vite-plugin-style-import';
export default function ImportStyle() {return createStyleImportPlugin({resolves: [AndDesignVueResolve()],// 自定义规则libs: [{libraryName: 'ant-design-vue',esModule: true,resolveStyle: (name) => {return `ant-design-vue/es/${name}/style/index`;},},],});
}
- 🍁整合插件:config/plugins/index.ts
/ @Description : 配置插件* @Author : SilenceLamb* @Version : V1.0.0*/
import type { Plugin } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import ImportComp from './modules/importComp';
import ImportDeps from './modules/importDeps';
import ImportStyle from './modules/importStyle';
export default function createVitePlugins(isBuild: boolean) {const vitePlugins: (Plugin | Plugin[])[] = [];// vue支持vitePlugins.push(vue());// JSX支持vitePlugins.push(vueJsx());// 自动按需引入依赖vitePlugins.push(ImportDeps());//自动按需引入组件vitePlugins.push(ImportComp());//自动引入样式vitePlugins.push(ImportStyle());return vitePlugins;
}
- 🍁vite配置:config/index.ts
/ @Description : Vite配置* @Author : SilenceLamb* @Version : V1.0.0*/
import createVitePlugins from '/#/plugins';
import resolve from '/#/aliases';
import serverConfig from '/#/server';
import { UserConfig } from 'vite';
import vite from '../src/utils/modules/vite';export default function viteConfig(mode: string, command: string, ViteEnv: object): UserConfig {const isBuild = vite.isBuild(command);return {['resolve']: resolve,['server']: serverConfig(ViteEnv.VITE_APP_BASE_API),['plugins']: createVitePlugins(isBuild),};
}
- 🍁vite.config.ts配置:vite.config.ts
import { UserConfig, ConfigEnv, loadEnv } from 'vite';
import viteUtils from '/@/utils/modules/vite';
import viteConfig from './config';
export default ({ command, mode }: ConfigEnv): UserConfig => {const ViteEnv = loadEnv(mode, process.cwd());//vite配置在config文件下const ViteConfig = viteConfig(command, mode, ViteEnv);return {//静态资源publicDir: 'public',//项目基础路径base: viteUtils.isDev(mode) ? '/' : '',//build时 去除 console和 debuggeresbuild: {drop: ['console', 'debugger'],},resolve: ViteConfig['resolve'],//插件配置plugins: ViteConfig['plugins'],//打包配置build: ViteConfig['build'],//跨域配置server: ViteConfig['server'],};
};
四、插件推荐
4.1🥑【自动重启】
💡
Tips
:通过监听文件修改,自动重启 vite 服务
- 最常用的场景就是监听 vite.config.js 和 .env.development 文件
yarn add npm i vite-plugin-restart -D
- 🥑配置插件:config/plugins/modules/autoRestartVite.ts
/ @Description : 自动重启Vite服务* @Author : SilenceLamb* @Version : V1.0.0*/
import ViteRestart from 'vite-plugin-restart'
export default function RestartVite() {return ViteRestart({restart: ['vite.config.ts'],})
}
💡
Tips
:自定义图标:以下插件都需要进行添加
vitePlugins.push(restartVite())
4.2🥑【引入图标】
yarn add vite-plugin-svg-icons -D
- 🥑配置插件:config/plugins/modules/svgIcon.ts
/ @Description : 自定义SvgIcon* @Author : SilenceLamb* @Version : V1.0.0*/
import { resolve } from 'path';
function pathResolve(dir: string) {return resolve(process.cwd(), '.', dir);
}
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';export default function svgIconPlugin() {return createSvgIconsPlugin({// 指定要缓存的图标文件夹iconDirs: [pathResolve('src/assets/svg')],// 指定符号 ID 格式symbolId: 'icon-[dir]-[name]',});
}
- 🥑SvgIcon组件:src/components/SvgIcon/index.vue
/*
* @Description :
* @Author : SilenceLamb
* @Version : V1.0.0
/*
* @Description : 自定义图标
* @Author : SilenceLamb
* @Version : V1.0.0
*/
<template><svg class="svg-icon" aria-hidden="true"><use :href="symbolId" aria-hidden="true" /></svg>
</template><script setup lang="ts">
import { computed } from 'vue';
const props = defineProps({prefix: {type: String,default: 'icon',},name: {type: String,required: true,},
});
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
</script>
<style scoped>
.svg-icon {width: 1em;height: 1em;vertical-align: -0.3em;/*因icon大小被设置为和字体大小一致,而span等标签的下边缘会和字体的基线对齐,故需设置一个往下的偏移比例,来纠正视觉上的未对齐效果*/fill: currentColor;/*定义元素的颜色,currentColor是一个变量,这个变量的值就表示当前元素的color值,如果当前元素未设置color值,则从父元素继承*/overflow: hidden;
}
</style>
<svg-icon name="order" class="icon" />
- 🥑配置插件:config/plugins/index.ts
/ @Description : 配置插件* @Author : SilenceLamb* @Version : V1.0.0*/
import type { Plugin } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import ImportComp from './modules/importComp';
import ImportDeps from './modules/importDeps';
import ImportStyle from './modules/importStyle';
import RestartVite from './modules/restartVite';
import SvgIcons from './modules/svgIcons';
import GZIPCompression from '/#/plugins/modules/compression';// @ts-ignore
export default function createVitePlugins(isBuild: boolean) {const vitePlugins: (Plugin | Plugin[])[] = [];// vue支持vitePlugins.push(vue());// JSX支持vitePlugins.push(vueJsx());// 自动按需引入依赖vitePlugins.push(ImportDeps());//自动按需引入组件vitePlugins.push(ImportComp());//自动引入样式vitePlugins.push(ImportStyle());//自动重启Vite服务vitePlugins.push(RestartVite());//自定义图标vitePlugins.push(SvgIcons());return vitePlugins;
}
五、打包配置
5.1⏰【Build配置】
- ⏰SvgIcon组件:config/build/index.ts
/ @Description : Build配置* @Author : SilenceLamb* @Version : V1.0.0*/
import { resolve } from 'path';
import constants from "/#/constant";
function pathResolve(dir: string) {return resolve(process.cwd(), '.', dir);
}
export default {//浏览器兼容性 "esnext"|"modules"target: 'modules',//启用/禁用 CSS 代码拆分cssCodeSplit: true,sourcemap: false,assetsInlineLimit: 10240,//生成静态资源的存放路径assetsDir: constants['common'].assetsDir,//打包文件生成位置outDir: constants['common'].outputDir,emptyOutDir: true,rollupOptions: {input: {main: pathResolve('index.html'),},output: {entryFileNames: `js/[name]-[hash].js`,chunkFileNames: `js/[name]-[hash].js`,assetFileNames: `[ext]/[name]-[hash].[ext]`,},},
};
/*
* build.target: 项目支持的浏览器目标
* build.polyfillDynamicImport: 是否动态的引入polyfill
* build.outDir: 输出的文件夹
* build.assetsDir: 指定生成静态资源的存放路径
* build.assetsInlineLimit: 小于这个大小的资源,以base64形式进行编码
* build.cssCodeSplit: 对css文件进行拆分,如果禁用,这个项目只有一个css文件
* build.sourceMap: 源文件映射,方便调试
* build.rollupOptions: 把options传给rollup
* build.commonjsOptions: 同上
* build.lib: 导出的是库模式,不是应用的模式
* build.manifest: 生成一个manifest文件。
* build.emptyOutDir: 构建的时候,是否主动清空目录
* build.chunkSizeWarningLimit: 如果打包的文件超过500k,就会给一个警告
* build.watch: 文件变化是否重新编译
*/
- ⏰Vite配置:config/index.ts
/ @Description : Vite配置* @Author : SilenceLamb* @Version : V1.0.0*/
import buildConfig from '/#/build';
import createVitePlugins from '/#/plugins';
import resolve from '/#/aliases';
import serverConfig from '/#/server';
import vite from '../src/utils/modules/vite';
import vite from '../src/utils/modules/vite';export default function viteConfig(mode: string, command: string, ViteEnv: object): UserConfig {const isBuild = vite.isBuild(command);return {['resolve']: resolve,['build']: buildConfig,['server']: serverConfig(VITE_APP_BASE_API),['plugins']: createVitePlugins(isBuild),};
}
- ⏰vite.config.ts配置:vite.config.ts
import { UserConfig, ConfigEnv, loadEnv } from 'vite';
import viteUtils from '/@/utils/modules/vite';
import viteConfig from './config';export default ({ command, mode }: ConfigEnv): UserConfig => {const ViteEnv = loadEnv(mode, process.cwd());//vite配置在config文件下const ViteConfig = viteConfig(mode, command, ViteEnv);return {//静态资源publicDir: 'public',//项目基础路径base: viteUtils.isDev(mode) ? '/' : '',//build时 去除 console和 debuggeresbuild: {drop: ['console', 'debugger'],},resolve: ViteConfig['resolve'],//插件配置plugins: ViteConfig['plugins'],//打包配置build: ViteConfig['build'],//跨域配置server: ViteConfig['server'],};
};
5.2⏰【GZIP压缩】
- ⏰GZIP压缩:config/plugins/modules/compression.ts
/ @Description : GZIP压缩* @Author : SilenceLamb* @Version : V1.0.0*/
import viteCompression from 'vite-plugin-compression';export default function GZIPCompression(isBuild: boolean) {return viteCompression({verbose: true, // //是否在控制台输出压缩结果,默认为 truedisable: !isBuild, //是否禁用压缩,默认为 falsedeleteOriginFile: isBuild, //压缩后是否删除原文件,默认为 falsethreshold: 0.8, //启用压缩的文件大小限制,单位是字节,默认为 0algorithm: 'gzip', //采用的压缩算法,默认是 gzipext: 'gz', //生成的压缩包后缀});
}
- ⏰配置插件:config/plugins/index.ts
/ @Description : 配置插件* @Author : SilenceLamb* @Version : V1.0.0*/
import type { Plugin } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import ImportComp from './modules/importComp';
import ImportDeps from './modules/importDeps';
import ImportStyle from './modules/importStyle';
import RestartVite from './modules/restartVite';
import SvgIcons from './modules/svgIcons';
import GZIPCompression from '/#/plugins/modules/compression';export default function createVitePlugins(isBuild: boolean) {const vitePlugins: (Plugin | Plugin[])[] = [];// vue支持vitePlugins.push(vue());// JSX支持vitePlugins.push(vueJsx());// 自动按需引入依赖vitePlugins.push(ImportDeps());//自动按需引入组件vitePlugins.push(ImportComp());//自定义图标vitePlugins.push(SvgIcons());//自动引入样式vitePlugins.push(ImportStyle());//自动重启Vite服务vitePlugins.push(RestartVite());//GIZP压缩vitePlugins.push(GZIPCompression(isBuild));return vitePlugins;
}
六、样式重置
- ✅ 安装依赖:sass sass-loader
npm install sass sass-loader -D
- 🌍样式整合:src/styles/index.scss
/*样式重置*/
@import 'normalize.css';
/*通用混入*/
@import './base';
/*全局过度*/
@import './common';
- 🌍全局引入:src/main.ts
import './styles/index.scss'; //全局样式
6.1🌍【基础样式】
- 🌍基础样式:src/styles/base/index.scss
/*通用混入*/
@import 'mixin';
/*全局过度*/
@import 'transition';
/*全局样式*/
@import 'global';
- 🌍混入样式:src/styles/base/mixin/index.scss
/*清除默认样式*/
@mixin resetStyles {* {margin: 0;padding: 0;box-sizing: border-box;outline: none !important;}
}/*清楚浮动*/
@mixin clearfix {&:after {content: '';display: table;clear: both;}
}/*相对大小*/
@mixin relative {position: relative;width: 100%;height: 100%;
}/*更改按钮颜色*/
@mixin colorBtn($color) {background: $color;&:hover {color: $color;&:before,&:after {background: $color;}}
}/*添加背景图片*/
@mixin bg-image($url) {background-image: url($url + '@2x.png');@media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3) {background-image: url($url + '@3x.png');}
}/*文本不换行*/
@mixin no-wrap() {text-overflow: ellipsis;overflow: hidden;white-space: nowrap;
}/*多行文本溢出*/
@mixin multiEllipsis($line: 2) {overflow: hidden;word-break: break-all;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: $line;-webkit-box-orient: vertical;
}/*透明度*/
@mixin opacity($opacity) {opacity: $opacity;$opacity-ie: $opacity * 100;filter: alpha(opacity=$opacity-ie); //IE8
}/*美化文本的选中*/
@mixin beauty-select($color, $bgcolor) {&::selection {color: $color;background-color: $bgcolor;}
}/*毛玻璃效果*/
@mixin blur($blur: 10px) {-webkit-filter: blur($blur);-moz-filter: blur($blur);-o-filter: blur($blur);-ms-filter: blur($blur);filter: progid:DXImageTransform.Microsoft.Blur(PixelRadius='${blur}');filter: blur($blur);*zoom: 1;
}/* 滤镜: 将彩色照片显示为黑白照片、保留图片层次*/
@mixin grayscale() {-webkit-filter: grayscale(100%);-moz-filter: grayscale(100%);-ms-filter: grayscale(100%);-o-filter: grayscale(100%);filter: grayscale(100%);
}/*文本居中*/
@mixin center($height: 100%) {height: $height;line-height: $height;text-align: center;
}/*修改背景色等*/
@mixin background($bg-color: #5f6970, $color: #000000, $font-weight: 400) {color: $color;font-weight: $font-weight;background-color: $bg-color;
}/*鼠标hover显示下划线*/
@mixin hoverLine($height: 2px, $color: #ffffff) {position: relative;&:hover::after {content: '';position: absolute;height: $height;width: 100%;background-color: $color;bottom: 0;left: 0;}
}/*滚动条*/
@mixin scrollBar($width: 6px, $height: 6px, $trackBg: rgba(255, 255, 255, 0), $thumbBg: #808080) {/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/&::-webkit-scrollbar {width: $width;height: $height;/* 防止遮挡内容 */padding-right: 20px;margin: 50px auto;overflow: hidden;}/*滚动条的轨道的两端按钮,允许通过点击微调小方块的位置*/&::-webkit-scrollbar-button {display: none;}/*边角,即两个滚动条的交汇处*/&::-webkit-scrollbar-corner {display: none;}/*定义滚动条轨道 内阴影+圆角*/&::-webkit-scrollbar-track {border-radius: 30px; /*轨道背景区域的圆角*/background-color: $trackBg; /*轨道的背景颜色*/}/*定义滑块 内阴影+圆角*/&::-webkit-scrollbar-thumb {border-radius: 12px;/*padding-box 表示背景裁剪到内边距框,这里也可以用 content-box,表示裁剪到内容框,默认为 border-box,表示裁剪到边框*/background-clip: padding-box;border-color: transparent;background: $thumbBg; /*滑块背景颜色*/}/* 悬浮更换滚动条颜色 */&::-webkit-scrollbar-thumb:hover {background: #493131;border-width: 0;}
}/* 文本可点击 */
@mixin clickable {-webkit-user-select: auto;-moz-user-select: auto;-ms-user-select: auto;user-select: auto;
}/*文本不可点击*/
@mixin noClickable {-webkit-user-select: none; /* Safari */-moz-user-select: none; /* Firefox */-ms-user-select: none; /* IE10+/Edge */user-select: none; /* Standard syntax */
}@mixin pct($pct) {width: #{$pct};position: relative;margin: 0 auto;
}@mixin triangle($width, $height, $color, $direction) {$width: $width/2;$color-border-style: $height solid $color;$transparent-border-style: $width solid transparent;height: 0;width: 0;@if $direction==up {border-bottom: $color-border-style;border-left: $transparent-border-style;border-right: $transparent-border-style;} @else if $direction==right {border-left: $color-border-style;border-top: $transparent-border-style;border-bottom: $transparent-border-style;} @else if $direction==down {border-top: $color-border-style;border-left: $transparent-border-style;border-right: $transparent-border-style;} @else if $direction==left {border-right: $color-border-style;border-top: $transparent-border-style;border-bottom: $transparent-border-style;}
}
6.2🌍【全局过度】
- 🌍样式重置:src/styles/base/transition/index.scss
// 全局过度 css/* fade */
.fade-enter-active,
.fade-leave-active {transition: opacity 0.28s;
}.fade-enter,
.fade-leave-active {opacity: 0;
}/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {transition: all 0.5s;
}.fade-transform-enter {opacity: 0;transform: translateX(-30px);
}.fade-transform-leave-to {opacity: 0;transform: translateX(30px);
}/* breadcrumb transition */
.breadcrumb-enter-active,
.breadcrumb-leave-active {transition: all 0.5s;
}.breadcrumb-enter,
.breadcrumb-leave-active {opacity: 0;transform: translateX(20px);
}.breadcrumb-move {transition: all 0.5s;
}.breadcrumb-leave-active {position: absolute;
}
6.3🌍【CSS 框架】
- 🌍通过 npm 安装 Tailwind
yarn add tailwindcss@latest -D
yarn add postcss@latest-D
yarn add autoprefixer@latest -D
- 🌍创建您的配置文件
npx tailwindcss init -p
- 🌍 配置文件:postcss.config.js
export default {plugins: {tailwindcss: {},autoprefixer: {},},
}
- 🌍 配置文件:tailwind.config.js
/ @type {import('tailwindcss').Config} */
export default {// 配置 Tailwind 来移除生产环境下没有使用到的样式声明purge: ['./index.html', './src//*.{vue,js,ts,jsx,tsx}'],content: [],theme: {extend: {},},plugins: [],
}
- 🌍 在您的 CSS 中引入 Tailwind
@tailwind base;
@tailwind components;
@tailwind utilities;
七、状态管理
- 🤡【官方文档】https://pinia.vuejs.org
💡
Tips
:pinia-plugin-persistedstate 插件可以使 Pinia 存储的持久性变得更简单和可配置
- 类似于 vuex-persistedstate 的 API
- 单个 Store 的配置
- 自定义存储方式和自定义序列化数据
7.1🤡【基本配置】
💡
Tips
:安装依赖
yarn add pinia@next
yarn add pinia-plugin-persistedstate
💡
Tips
:src文件夹下新建store文件夹,store文件夹下新建index.ts
- 🤡 vuex-persistedstate:src/store/index.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedState from 'pinia-plugin-persistedstate'const pinia = createPinia()
pinia.use(piniaPluginPersistedState)export default pinia
- 🤡 main.ts:main.ts
import { createApp } from 'vue'
import App from './App.vue'
import pinia from "/@/store"; //状态管理
const app = createApp(App)
app.use(pinia)
app.mount('#app')
💡
Tips
:store文件夹下新建modules文件夹:用于模块化管理
- 🤡定义一个 Store:src/store/modules/test.ts - 未持久化
/* @Description : Pinia示例-未持久化* @Author : SilenceLamb* @Version : V1.0.0*/
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', () => {const count = ref(0)const doubleCount = computed(() => count.value * 2)function increment() {count.value++}return { count, doubleCount, increment }
})
- √🤡定义一个 Store:src/store/modules/test.ts - 持久化
/* @Description : Pinia示例-持久化* @Author : SilenceLamb* @Version : V1.0.0*/
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', () => {const count = ref(0)const doubleCount = computed(() => count.value * 2)function increment() {count.value++}return { count, doubleCount, increment }
}, {persist: true,
})
💡
Tips
:当点击 increment 按钮时,响应属性 count 和计算属性 doubleCount 都会改变
- 刷新页面后可以看到,它们也都显示为刷新之前的数据,说明数据已经被持久化了
<template>{{ countStore.count }}<hr>{{ countStore.doubleCount }}<hr><button @click="countStore.increment">increment</button>
</template>
<script setup>
import { useCounterStore } from "../../store/modules/user"
const countStore = useCounterStore()
</script>
💡
Tips
:插件预先配置了以下内容 - 🚀参考链接
- localStorage 作为存储
- store.$id 作为存储的默认键,即 defineStore 的第一个参数
- JSON.stringify/JSON.parse 作为序列化器/反序列化器
- 所有属性都会被持久化到本地存储中
7.2🤡【基本示例】
🤡
创建三个文件夹
:模块化管理
- 🤡Pinia - 持久键值:src/store/constant/index.ts
/* @Description : Pinia-持久性Key* @Author : SilenceLamb* @Version : V1.0.0*/
export const ADMIN_INFO = 'AdminInfo';
- 🤡Store - 变量类型:src/store/interface/index.ts
/* @Description : store-变量类型* @Author : SilenceLamb* @Version : V1.0.0*/
export interface AdminInfo {userName: string;nickName: string;avatar: string;token: string;refreshToken: string;
}
- 🤡Pinia - 实例管理(Option Store):src/store/modules/adminInfo.ts
/* @Description : Pinia示例* @Author : SilenceLamb* @Version : V1.0.0*/
import { defineStore } from 'pinia';
import { ADMIN_INFO } from '/@/store/constant';
import { AdminInfo } from '/@/store/interface';export const useAdminInfoState = defineStore({id: 'AdminInfo',state: (): AdminInfo => {return {userName: 'userName',nickName: 'nickName',avatar: 'avatar',token: 'tokena',refreshToken: 'refreshToken',};},actions: {/* @Description :设置state全部值* @param state*/setState(state: AdminInfo) {this.$state = { ...this.$state, ...state };},/* @Description : 通过键设置值* @param value 值* @param key state的键*/setStateByKey(key: any, value: string) {this[key] = value;},/* 设置Token* @param value token* @param key state的键*/setToken(key: 'token' | 'refreshToken', value: string) {this[key] = value;},/* 获取Token* @param key state的键*/getToken(key: 'auth' | 'refresh' = 'auth') {return key === 'auth' ? this.token : this.refreshToken;},/* @Description : 移除Token*/removeToken() {this.token = '';this.refreshToken = '';},},persist: {key: ADMIN_INFO,},
});
7.3🤡【基本使用】
🤡直接获取:获取state
<template><div>{{useAdminInfo.userName}}</div><div>{{useAdminInfo.nickName}}</div><div>{{useAdminInfo.avatar}}</div><div>{{useAdminInfo.token}}</div>
</template>
<script setup lang="ts">
import { useAdminInfoState } from '/@/store/modules/adminInfo'
const useAdminInfo = useAdminInfoState()
</script>
🤡computed获取:获取state
<template><div>{{userName}}</div>
</template>
<script setup lang="ts">
import { useAdminInfoState } from '/@/store/modules/adminInfo'
const useAdminInfo = useAdminInfoState()
//响应式的
const userName=computed(()=>useAdminInfo.userName)
</script>
🤡直接使用:直接使用getters
export const useStore = defineStore('main', {state: () => ({count: 0,}),getters: {// 自动推断出返回类型是一个 numberdoubleCount(state) {return state.count * 2},// 返回类型必须明确设置doublePlusOne(): number {// 整个 store 的 自动补全和类型标注 ✨return this.doubleCount + 1},},
})
<script setup>
const store = useCounterStore()
store.count = 3
store.doubleCount // 6
</script>
🤡直接使用:直接使用getters
<script setup lang="ts">
import { useAdminInfoState } from '/@/store/modules/adminInfo'
const useAdminInfo = useAdminInfoState()
useAdminInfo.getToken()
</script>
7.4🤡【定义插件】
- 为 store 添加新的属性
- 定义 store 时增加新的选项
- 为 store 增加新的方法
- 包装现有的方法
- 改变甚至取消 action
- 实现副作用,如本地存储
- 仅应用插件于特定 store
🤡 Pinia 插件是一个函数,可以选择性地返回要添加到 store 的属性。它接收一个可选参数,即 context。
export function myPiniaPlugin(context) {context.pinia // 用 `createPinia()` 创建的 pinia。 context.app // 用 `createApp()` 创建的当前应用(仅 Vue 3)。context.store // 该插件想扩展的 storecontext.options // 定义传给 `defineStore()` 的 store 的可选对象。// ...
}
- 然后用 pinia.use() 将这个函数传给 pinia:
pinia.use(myPiniaPlugin)
🤡 自定义持久化插件
- 🤡简洁版持久化插件:src/store/plugin/persistence.ts
/* @Description : 简洁版持久化插件:使用storeId作为Key进行持久化* @Author : SilenceLamb* @Version : V1.0.0*/
import {PiniaPluginContext} from "pinia";export function persistenceState(context:PiniaPluginContext) {/* @Description : 每次启动时都执行* 根据stateId从localStorage获取数据*/const currentState = JSON.parse(localStorage.getItem(context.store.$id)||'{}')context.store.$patch(currentState)/* @Description : 每次state发生变化时执行* @param {state} : oldState 原来的数据* @param {state} : newState 修改的数据* 都要把state保存到localStorage里面*/context.store.$subscribe((oldState,newState)=>{console.log(newState)localStorage.setItem(oldState.storeId, JSON.stringify(newState))},{detached : true})
}
- 🤡使用持久化插件:src/store/index.ts
/* @Description : Pinia 存储的持久性* @Author : SilenceLamb* @Version : V1.0.0*/
import { createPinia } from 'pinia';
import {persistenceState} from "/@/store/plugin/persistence";const pinia = createPinia();
pinia.use(persistenceState);export default pinia;
八、路由配置
- 🍑 官方链接:https://router.vuejs.org
8.1🍑【路由配置】
- 🍑 yarn命令安装
yarn add vue-router@4
💡
Tips
:src文件夹下新建router文件夹,router文件夹下新建static.ts
- 🍑路由配置:src/router/static.ts
/* @Description : 路由配置* @Author : SilenceLamb* @Version : V1.0.0*/
import { RouteRecordRaw } from 'vue-router';export const constantRoutes: RouteRecordRaw [] = [{path: '/',name: 'Index',component: () => import('/@/views/index/index.vue'),},
];export default {constantRoutes};
💡
Tips
:在main.ts中,引入router并注册
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
const app = createApp(App)
app.use(router)
app.mount('#app')
💡
Tips
:在App.vue中设置路由展现出口
<template><router-view />
</template>
8.2🍑【路由守卫】
💡
Tips
:src文件夹下新建router文件夹,router文件夹下新建index.ts
- 🍑简单的路由守卫:src/router/index.ts
/* @Description : 路由守卫* @Author : SilenceLamb* @Version : V1.0.0*/
import {createRouter, createWebHistory} from 'vue-router';
import {constantRoutes} from './static';
import {useAdminInfoState} from '/@/store/modules/adminInfo';
//引入进度条样式
import 'nprogress/nprogress.css';const router = createRouter({history: createWebHistory(),routes: constantRoutes,
});
/* @Description 全局前置守卫* 当一个导航触发时,全局前置守卫按照创建顺序调用* @param to: 即将要进入的目标 用一种标准化的方式* @param from: 当前导航正要离开的路由 用一种标准化的方式* @return {boolean} false: 取消当前的导航 |true 通过一个路由地址跳转到一个不同的地址*/
/* 添加白名单*/
const whiteList = ['/login']; // no redirect whitelist
// @ts-ignore
router.beforeEach((to, from, next) => {console.log(useAdminInfoState().getToken())if (useAdminInfoState().getToken()) {/* has token*/if (to.path === '/login') {next({path: '/'})} else if (useAdminInfoState().userName === null) {//获取用户信息} else {next()}} else {// 没有tokenif (whiteList.indexOf(to.path) !== -1) {// 在免登录白名单,直接进入next()} else {next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页}}
})/* @Description 全局后置钩子* 钩子不会接受 next 函数也不会改变导航本身*/
router.afterEach(() => {
});export default router;
8.3🍑【组件跳转】
- 🍑 所以我们不能再直接访问 this.router或this.router 或 this.router或this.route
- 🍑作为替代,我们使用 useRouter 和 useRoute 函数
import { useRouter, useRoute } from 'vue-router'export default {setup() {const router = useRouter()const route = useRoute()function pushWithQuery(query) {router.push({name: 'search',query: {...route.query,},})}},
}
8.4🍑【组件守卫】
💡
Tips
:组件内路由守卫
- 🍑新增组合式api:可以替代原有的组件内守卫
- onBeforeRouteLeave(离开当前页面路由时触发)
- onBeforeRouteUpdate(路由更新时触发)
💡
Tips
你可以在路由组件内直接定义路由导航守卫(传递给路由配置的)
beforeRouteEnter(to, from) {// 在渲染该组件的对应路由被验证前调用// 不能获取组件实例 `this` !// 因为当守卫执行时,组件实例还没被创建!},beforeRouteUpdate(to, from) {// 在当前路由改变,但是该组件被复用时调用// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`},beforeRouteLeave(to, from) {// 在导航离开渲染该组件的对应路由时调用// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`},
- 🍑页面使用
import { useRouter,useRoute } from 'vue-router';const go=()=>{const Router=useRouter()const Route=useRoute()Router.push({name:'/login',query:{id:'123456'}})}
8.5🍑【过渡动效】
- 🍑整个布局过度动效:src/router/static.ts
<router-view v-slot="{ Component }"><transition name="fade"><component :is="Component" /></transition>
</router-view>
- 🍑单个路由的过渡:src/router/static.ts
const routes = [{path: '/custom-transition',component: PanelLeft,meta: { transition: 'slide-left' },},{path: '/other-transition',component: PanelRight,meta: { transition: 'slide-right' },},
]
- 🍑 你可以将元信息和动态的 name 结合在一起,放在 < transition > 上
<router-view v-slot="{ Component, route }"><!-- 使用任何自定义过渡和回退到 `fade` --><transition :name="route.meta.transition || 'fade'"><component :is="Component" /></transition>
</router-view>
九、封装工具类
9.1🛞【基本配置】
💡
Tips
:在src文件下创建utils,文件夹下新建index.ts文件夹:用于扫描ts文件
/ @Description :* @Author : SilenceLamb* @Version : V1.0.0*/
import fileNameUtils from './modules/fileName';
export default {install(app) {const modules = import.meta.glob('./modules/*.ts', { eager: true });for (const path in modules) {const method: any = modules[path],fileName = fileNameUtils.getFileName(path);if (method.default !== undefined) {Object.keys(method).forEach(() => {app.config.globalProperties['$' + fileName] = method.default;});} else {// 将该方法注册为 $fileName 的全局方法app.config.globalProperties['$' + fileName] = method;}}},
};
💡
Tips
:main.ts全局引入
- 🛞 main.ts:main.ts
import { createApp } from 'vue'
import App from './App.vue'
import utils from '/@/utils';
const app = createApp(App)
app.use(utils,true)
app.use(router)
app.mount('#app')
9.2🛞【基本使用】
🛞 Tips:app.use(utils,true)
- 🛞 < script setup lang=“ts” >
<template><div class="app-container"><a-button @click="message">message</a-button></div>
</template>
<script setup lang="ts">
const model = inject('model');
function message() {model.message('消息提示', 'success');
}
</script>
- 🛞 < script lang=“ts” >
<template >
<div class="app-container"><a-button @click="message">message</a-button>
</div>
</template>
<script lang="ts">export default{setup(){const model = inject('model');return {model}},methods:{message(){this.model.message('消息提示', 'success');}}
}
</script>
💡
Tips
:🛞app.use(utils,false)
<template >
<div class="app-container"><a-button @click="message">message</a-button>
</div>
</template>
<script lang="ts">export default{methods:{message(){this.$model.message('消息提示', 'success');}}
}
</script>
9.3🛞【常用工具】
🛞
Tips
:本地缓存 | 会话缓存:src/utils/modules/cache.ts
/ @Description : 本地缓存 | 会话缓存* @Author : SilenceLamb* @Version : V1.0.0*/
/* window.localStorage* @method set 设置* @method get 获取* @method remove 移除* @method clear 移除全部*/
export const Local = {set(key: string, val: any) {window.localStorage.setItem(key, JSON.stringify(val));},get(key: string) {const json: any = window.localStorage.getItem(key);return JSON.parse(json);},remove(key: string) {window.localStorage.removeItem(key);},clear() {window.localStorage.clear();},
};/* window.sessionStorage* @method set 设置会话缓存* @method get 获取会话缓存* @method remove 移除会话缓存* @method clear 移除全部会话缓存*/
export const Session = {set(key: string, val: any) {window.sessionStorage.setItem(key, JSON.stringify(val));},get(key: string) {const json: any = window.sessionStorage.getItem(key);return JSON.parse(json);},remove(key: string) {window.sessionStorage.removeItem(key);},clear() {window.sessionStorage.clear();},
};
🛞
Tips
:消息提示:src/utils/modules/model.ts
/ @Description : 用于消息提示、确认消息和提交内容* @Version : V1.0.0* @Author : SilenceLamb*/
import { message, notification, Modal, Spin } from 'ant-design-vue';
let loadingInstance;export default {// 消息提示message(content, type: 'error' | 'success' | 'warning' | 'info') {message[type](content).then();},// 弹出提示 -(弹框)alert(title: string, content: any, type: 'error' | 'success' | 'warning' | 'info') {Modal[type]({title: title,content: content,});},// 通知提示 -(通知)notify(title: any, content: any, type: 'error' | 'success' | 'warning' | 'info') {notification[type]({message: title,description: content,});},// 确认窗体 -(窗体)confirm(title: any, content: any) {return Modal.confirm({title: title,content: content,okText: '确认',cancelText: '取消',type: 'warning',});},
};
🛞
Tips
:参数处理:src/utils/modules/params.ts
/ @Description : 参数处理工具* @Author : SilenceLamb* @Version : V1.0.0*/
export default {/* @Description :请求参数处理* @param params* @return {string}*/tansParams(params:any):any {let result = ''for (const propName of Object.keys(params)) {const value = params[propName]const part = `${encodeURIComponent(propName)}=`if (value !== null && value !== '' && typeof value !== 'undefined') {if (typeof value === 'object') {for (const key of Object.keys(value)) {// eslint-disable-next-line max-depthif (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {const params = `${propName}[${key}]`const subPart = `${encodeURIComponent(params)}=`result += `${subPart + encodeURIComponent(value[key])}&`}}} else {result += `${part + encodeURIComponent(value)}&`}}}return result},
}
🛞
Tips
:Token工具:src/utils/modules/token.ts
/ @Description : Token管理工具* @Author : SilenceLamb* @Version : V1.0.0*/
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export default {/* @Description 获取Token* @returns token*/getToken() {return Cookies.get(TokenKey)},/* @Description 设置Token* @param {*} token*/setToken(token) {return Cookies.set(TokenKey, token)},/* @Description 移除Token*/removeToken() {return Cookies.remove(TokenKey)},
}
🛞
Tips
:获取文件名:src/utils/modules/fileName.ts
/ @Description : 获取文件名工具* @Author : SilenceLamb* @Version : V1.0.0*/
export default {/* @Description 获取最后一个文件夹* @param {string} filePath* @returns {string} fileName 子文件夹名*/getLastFileName(filePath) {const matches = filePath.substring(0, filePath.lastIndexOf('/')).split('/').pop();return matches;},/* @Description 获取文件名* @param {string} filePath* @returns {string} fileName 文件名*/getFileName(filePath) {// 将路径拆分为数组const parts = filePath.split('/'),// 获取数组中的最后一个元素fileName = parts[parts.length - 1];// 获取文件名并返回return fileName.replace('.ts', '');},
};
十、Layout布局
✨
Tips
:Layout布局:src/layout/index.vue
<template><div class="app-wrapper"><app-main /><back-to-top /></div>
</template><style lang="scss" scoped>
@import 'src/styles/index';.app-wrapper {@include clearfix;position: relative;height: 100%;width: 100%;
}
</style>
✨
Tips
:主要区域组件:src/layout/components/appMain/index.vue
<template><section class="app-main"><router-view v-slot="{ Component }"><transition name="fade-transform" mode="out-in"><component :is="Component" :key="$route.path" /></transition></router-view></section>
</template><script>
export default { name: 'AppMain' };
</script><style lang="scss" scoped>
.app-main {/*50 = navbar */min-height: 100vh;width: 100%;position: relative;overflow: hidden;
}
</style><style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {.fixed-header {padding-right: 15px;}
}
</style>
✨
Tips
:路由引入Layout布局:
import Layout from '/@/layout/index.vue';
{path: '/',redirect: '/index',component: Layout,children: [{ name: 'index', path: '/index', component: () => import('/@/views/index/index.vue') }],},