当前位置: 首页 > news >正文

vue-admin-template vue-cli 4升5(vue2版)

升级步骤与优化方案(升级Vue CLI到4.5.19)

第一步:准备工作 (非常重要!)

  1. 备份项目: 在进行任何重大升级之前,请务必使用 Git 或其他方式备份你的整个项目目录。如果升级过程中出现问题,你可以轻松回滚。

    git add .
    git commit -m "备份: 升级Vue CLI前的状态"
  2. 检查当前版本: 确认你当前的 Vue CLI 版本。

    vue --version
    # 或
    vue -V
  3. 更新全局 Vue CLI: 首先确保你全局安装的 @vue/cli 是最新的 5.x 版本。

    npm uninstall -g @vue/cli
    # 或者
    # yarn global remove @vue/clinpm install -g @vue/cli@latest
    # 或者
    # yarn global add @vue/cli@latest# 验证版本
    vue --version # 应该显示 5.x.x

第二步:在项目中进行升级操作

Vue CLI 提供了一个专门的升级命令来迁移项目。

一.在项目根目录执行升级命令:

vue upgrade

这个命令会检查你的 package.json 中的依赖,并尝试将它们更新到与 Vue CLI 5 兼容的最新版本。它会交互式地询问你是否要更新每个包。

注意: 这个过程有时并不完美,特别是对于像 vue-admin-template 这样有大量自定义配置的项目。你可能需要手动解决一些冲突。

二.项目初始化与依赖升级

 首先更新 package.json 文件:

{"name": "vue-admin-template","version": "5.0.0","description": "A vue admin template with Vue CLI 5","scripts": {"dev": "vue-cli-service serve","build:prod": "vue-cli-service build","build:stage": "vue-cli-service build --mode staging","preview": "node build/index.js --preview","lint": "eslint --ext .js,.vue src","test:unit": "vue-cli-service test:unit","test:e2e": "vue-cli-service test:e2e"},"dependencies": {"vue": "^2.7.14","vue-router": "^3.6.5","vuex": "^3.6.2","axios": "^1.4.0","element-ui": "^2.15.13","js-cookie": "^3.0.1","normalize.css": "^8.0.1","nprogress": "^0.2.0","vue-count-to": "^1.0.13"},"devDependencies": {"@vue/cli-service": "^5.0.8","vue-template-compiler": "^2.7.14","sass": "^1.63.6","sass-loader": "^13.3.2","compression-webpack-plugin": "^10.0.0","script-ext-html-webpack-plugin": "^2.1.5","babel-plugin-component": "^1.1.1","@babel/core": "^7.22.5","@babel/preset-env": "^7.22.5","babel-eslint": "^10.1.0","@vue/cli-plugin-babel": "^5.0.8","@vue/cli-plugin-eslint": "^5.0.8","@vue/cli-plugin-unit-jest": "^5.0.8","@vue/cli-plugin-e2e-cypress": "^5.0.8","@vue/test-utils": "^1.3.3","eslint": "^7.32.0","eslint-plugin-vue": "^7.20.0","chalk": "^4.1.2","connect": "^3.7.0","runjs": "^4.4.2","webpack-bundle-analyzer": "^4.8.0"},"engines": {"node": ">=14.0.0","npm": ">=6.0.0"},"browserslist": ["> 1%","last 2 versions","not dead"]
}

三. Vue CLI 配置文件 (vue.config.js)

const { defineConfig } = require('@vue/cli-service')
const path = require('path')
const webpack = require('webpack')function resolve(dir) {return path.join(__dirname, dir)
}module.exports = defineConfig({transpileDependencies: true,lintOnSave: process.env.NODE_ENV === 'development',productionSourceMap: false,// 修正后的配置configureWebpack: {resolve: {alias: {'@': resolve('src')}},plugins: [new webpack.ProvidePlugin({'window.Quill': 'quill/dist/quill.js','Quill': 'quill/dist/quill.js'})]},chainWebpack(config) {config.plugins.delete('preload')config.plugins.delete('prefetch')config.module.rule('svg').exclude.add(resolve('src/icons')).end()config.module.rule('icons').test(/\.svg$/).include.add(resolve('src/icons')).end().use('svg-sprite-loader').loader('svg-sprite-loader').options({symbolId: 'icon-[name]'}).end()config.when(process.env.NODE_ENV !== 'development',config => {config.plugin('ScriptExtHtmlWebpackPlugin').after('html').use('script-ext-html-webpack-plugin', [{inline: /runtime\..*\.js$/}]).end()config.optimization.splitChunks({chunks: 'all',cacheGroups: {libs: {name: 'chunk-libs',test: /[\\/]node_modules[\\/]/,priority: 10,chunks: 'initial'},elementUI: {name: 'chunk-elementUI',priority: 20,test: /[\\/]node_modules[\\/]_?element-ui(.*)/},commons: {name: 'chunk-commons',test: resolve('src/components'),minChunks: 3,priority: 5,reuseExistingChunk: true}}})config.optimization.runtimeChunk('single')})},// 修正后的 devServer 配置devServer: {port: 9527,open: true,hot: true,client: {overlay: {warnings: false,errors: true,progress: true // 如果需要进度显示,可以添加以下配置}},proxy: {'/api': {target: 'http://localhost:8080',changeOrigin: true,pathRewrite: {'^/api': ''}}}}
})

四. Babel 配置文件 (babel.config.js)

module.exports = {presets: ['@vue/cli-plugin-babel/preset'],plugins: [['component',{libraryName: 'element-ui',styleLibraryName: 'theme-chalk'}]]
}

五. 环境变量配置

创建 .env.development:

NODE_ENV=development
VUE_APP_BASE_API=/api
VUE_APP_BASE_URL=http://localhost:9527

创建 .env.production:

NODE_ENV=production
VUE_APP_BASE_API=/api
VUE_APP_BASE_URL=https://production-domain.com

创建 .env.staging:

NODE_ENV=production
VUE_APP_BASE_API=/api
VUE_APP_BASE_URL=https://staging-domain.com

六.处理 Webpack 5 的 polyfill 问题

Webpack 5 不再自动包含 Node.js 核心模块的 polyfill。您需要在项目中配置相应的 polyfill

1.安装所需的 polyfill 包
npm install path-browserify -D
npm install crypto-browserify -D
npm install stream-browserify -D
npm install buffer -D
npm install process -D
 2.更新 vue.config.js 配置
const { defineConfig } = require('@vue/cli-service')
const path = require('path')
const webpack = require('webpack')function resolve(dir) {return path.join(__dirname, dir)
}module.exports = defineConfig({transpileDependencies: true,lintOnSave: process.env.NODE_ENV === 'development',productionSourceMap: false,configureWebpack: {resolve: {alias: {'@': resolve('src'),// 添加其他常用别名'components': resolve('src/components'),'views': resolve('src/views'),'utils': resolve('src/utils'),'api': resolve('src/api')},// 添加 polyfill 配置fallback: {"path": require.resolve("path-browserify"),"crypto": require.resolve("crypto-browserify"),"stream": require.resolve("stream-browserify"),"buffer": require.resolve("buffer/"),"util": require.resolve("util/"),"process": require.resolve("process/browser")}},plugins: [new webpack.ProvidePlugin({'window.Quill': 'quill/dist/quill.js','Quill': 'quill/dist/quill.js',// 提供全局的 process 和 Bufferprocess: 'process/browser',Buffer: ['buffer', 'Buffer']})]},chainWebpack(config) {config.plugins.delete('preload')config.plugins.delete('prefetch')config.module.rule('svg').exclude.add(resolve('src/icons')).end()config.module.rule('icons').test(/\.svg$/).include.add(resolve('src/icons')).end().use('svg-sprite-loader').loader('svg-sprite-loader').options({symbolId: 'icon-[name]'}).end()config.when(process.env.NODE_ENV !== 'development',config => {config.plugin('ScriptExtHtmlWebpackPlugin').after('html').use('script-ext-html-webpack-plugin', [{inline: /runtime\..*\.js$/}]).end()config.optimization.splitChunks({chunks: 'all',cacheGroups: {libs: {name: 'chunk-libs',test: /[\\/]node_modules[\\/]/,priority: 10,chunks: 'initial'},elementUI: {name: 'chunk-elementUI',priority: 20,test: /[\\/]node_modules[\\/]_?element-ui(.*)/},commons: {name: 'chunk-commons',test: resolve('src/components'),minChunks: 3,priority: 5,reuseExistingChunk: true}}})config.optimization.runtimeChunk('single')})},devServer: {port: 9527,open: true,hot: true,client: {overlay: {warnings: false,errors: true}},proxy: {'/api': {target: 'http://localhost:8080',changeOrigin: true,pathRewrite: {'^/api': ''}}}}
})
3.检查并修复 Sidebar 组件中的路径引用

在 src/layout/components/Sidebar/index.vue 或相关文件中,查找并修改使用 path 模块的代码:

修改前:

const path = require('path')

修改后:

// 对于浏览器环境,使用 URL 相关的 API 替代 path 模块
// 或者使用以下方式引入 polyfill
import { resolve } from 'path-browserify'// 或者使用浏览器原生的 URL API
function resolvePath(...paths) {return paths.join('/').replace(/\/+/g, '/')
}
4.创建浏览器兼容的路径工具函数

在 src/utils/path.js 中创建浏览器兼容的路径工具:

/*** 浏览器环境下的路径处理工具*/// 解析路径(模拟 path.resolve)
export function resolvePath(...paths) {const joinedPath = paths.join('/').replace(/\/+/g, '/')const segments = joinedPath.split('/')const result = []for (const segment of segments) {if (segment === '..') {result.pop()} else if (segment !== '.' && segment !== '') {result.push(segment)}}return result.join('/')
}// 获取目录名(模拟 path.dirname)
export function dirname(path) {const lastSlashIndex = path.lastIndexOf('/')return lastSlashIndex === -1 ? '.' : path.slice(0, lastSlashIndex)
}// 获取文件名(模拟 path.basename)
export function basename(path, ext = '') {const lastSlashIndex = path.lastIndexOf('/')const filename = lastSlashIndex === -1 ? path : path.slice(lastSlashIndex + 1)if (ext && filename.endsWith(ext)) {return filename.slice(0, -ext.length)}return filename
}// 获取扩展名(模拟 path.extname)
export function extname(path) {const basename = path.slice(path.lastIndexOf('/') + 1)const dotIndex = basename.lastIndexOf('.')return dotIndex === -1 ? '' : basename.slice(dotIndex)
}// 路径连接(模拟 path.join)
export function joinPath(...paths) {return paths.join('/').replace(/\/+/g, '/')
}export default {resolve: resolvePath,dirname,basename,extname,join: joinPath
}
5.在 Sidebar 组件中使用新的路径工具

修改 src/layout/components/Sidebar 中的相关文件:

// 替换原来的 path 引入
// const path = require('path') // 删除这行// 使用新的路径工具
import path from '@/utils/path'// 或者直接使用工具函数
import { resolvePath, joinPath } from '@/utils/path'

七.缺少 svg-sprite-loader 依赖解决方案

报错:

Failed to resolve loader: svg-sprite-loader You may need to install it.

1. 安装所需的 loader

npm install svg-sprite-loader@6.0.11 -D
npm install svgo@1.3.2 -D
npm install svgo-loader@3.0.0 -D

2. 更新 vue.config.js 配置

const { defineConfig } = require('@vue/cli-service')
const path = require('path')
const webpack = require('webpack')function resolve(dir) {return path.join(__dirname, dir)
}module.exports = defineConfig({transpileDependencies: true,lintOnSave: process.env.NODE_ENV === 'development',productionSourceMap: false,configureWebpack: {resolve: {alias: {'@': resolve('src'),'components': resolve('src/components'),'views': resolve('src/views'),'utils': resolve('src/utils'),'api': resolve('src/api')},fallback: {"path": require.resolve("path-browserify"),"crypto": require.resolve("crypto-browserify"),"stream": require.resolve("stream-browserify"),"buffer": require.resolve("buffer/"),"util": require.resolve("util/"),"process": require.resolve("process/browser")}},plugins: [new webpack.ProvidePlugin({'window.Quill': 'quill/dist/quill.js','Quill': 'quill/dist/quill.js',process: 'process/browser',Buffer: ['buffer', 'Buffer']})]},chainWebpack(config) {config.plugins.delete('preload')config.plugins.delete('prefetch')// SVG 图标处理配置config.module.rule('svg').exclude.add(resolve('src/icons')).end()config.module.rule('icons').test(/\.svg$/).include.add(resolve('src/icons')).end().use('svg-sprite-loader').loader('svg-sprite-loader').options({symbolId: 'icon-[name]'}).end()// 添加 svgo-loader 优化 SVG.use('svgo-loader').loader('svgo-loader').options({plugins: [{ name: 'removeAttrs', params: { attrs: '(fill|stroke)' } },{ name: 'removeTitle', active: true },{ name: 'removeDimensions', active: true }]}).end()config.when(process.env.NODE_ENV !== 'development',config => {config.plugin('ScriptExtHtmlWebpackPlugin').after('html').use('script-ext-html-webpack-plugin', [{inline: /runtime\..*\.js$/}]).end()config.optimization.splitChunks({chunks: 'all',cacheGroups: {libs: {name: 'chunk-libs',test: /[\\/]node_modules[\\/]/,priority: 10,chunks: 'initial'},elementUI: {name: 'chunk-elementUI',priority: 20,test: /[\\/]node_modules[\\/]_?element-ui(.*)/},commons: {name: 'chunk-commons',test: resolve('src/components'),minChunks: 3,priority: 5,reuseExistingChunk: true}}})config.optimization.runtimeChunk('single')})},devServer: {port: 9527,open: true,hot: true,client: {overlay: {warnings: false,errors: true}},proxy: {'/api': {target: 'http://localhost:8080',changeOrigin: true,pathRewrite: {'^/api': ''}}}}
})

3. 创建或更新 icons 索引文件

确保 src/icons/index.js 文件存在:

import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon' // svg component// register globally
Vue.component('svg-icon', SvgIcon)const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

4. 创建 SvgIcon 组件

创建 src/components/SvgIcon/index.vue

<template><svg :class="svgClass" aria-hidden="true" v-on="$listeners"><use :xlink:href="iconName" /></svg>
</template><script>
export default {name: 'SvgIcon',props: {iconClass: {type: String,required: true},className: {type: String,default: ''}},computed: {iconName() {return `#icon-${this.iconClass}`},svgClass() {if (this.className) {return 'svg-icon ' + this.className} else {return 'svg-icon'}}}
}
</script><style scoped>
.svg-icon {width: 1em;height: 1em;vertical-align: -0.15em;fill: currentColor;overflow: hidden;
}
</style>

5. 添加全局 SVG 样式

在 src/styles 目录下创建或更新 SVG 样式:

// src/styles/svg.scss
.svg-icon {width: 1em;height: 1em;vertical-align: -0.15em;fill: currentColor;overflow: hidden;
}// 在 main.scss 中引入
@import './svg.scss';

6. 创建示例 SVG 图标

在 src/icons/svg 目录下创建一些示例 SVG 文件:

src/icons/svg/dashboard.svg

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"/>
</svg>

src/icons/svg/user.svg

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
</svg>

8. 使用 SVG 图标组件

在组件中使用 SVG 图标:

<template><div><svg-icon icon-class="dashboard" /><svg-icon icon-class="user" class-name="custom-class" /></div>
</template><script>
export default {name: 'ExampleComponent'
}
</script>
9.element字体图标显示为框框

 修改vue.config.js,注意如果字体图标不显示可能是字体配置与vuecli的默认配置起冲突到时删除我们自定义的配置即可:

   // 删除以下配置即可config.module.rule('fonts').test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/).use('url-loader').loader('url-loader').options({// 修改 limit 值,小于 10k 的字体文件转换为 base64,大于 10k 的则拷贝文件limit: 100000,// 根据项目结构修改 name。这里是将字体文件输出到 'fonts' 目录下name: 'fonts/[name].[hash:8].[ext]',esModule: false // 有时需要设置这个来避免ES模块转换问题}).end()

八..eslintrc.js完整配置

1.必需的依赖安装
npm install eslint@7.32.0 -D
npm install eslint-plugin-vue@7.20.0 -D
npm install eslint-config-standard@16.0.3 -D
npm install eslint-plugin-import@2.27.5 -D
npm install eslint-plugin-node@11.1.0 -D
npm install eslint-plugin-promise@5.2.0 -D
npm install babel-eslint@10.1.0 -D
# 安装 Vue 特定的配置
npm install @vue/eslint-config-standard@6.1.0 -D
2. 修正后的 .eslintrc.js 配置
module.exports = {root: true,env: {node: true,browser: true,es6: true},extends: ['plugin:vue/essential', '@vue/standard'],parserOptions: {parser: 'babel-eslint',ecmaVersion: 2020,sourceType: 'module'},rules: {// 自定义规则'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off','no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off','no-unused-vars': 'warn','no-trailing-spaces': 'warn','no-useless-escape': 'off',indent: ['error', 2],quotes: ['error', 'single'],semi: ['error', 'never'],'comma-dangle': ['error', 'only-multiline'],'prefer-promise-reject-errors': 'off',// 'space-before-function-paren': ['error', 'always'],'space-before-function-paren': 0,'vue/multi-word-component-names': 'off','vue/require-default-prop': 'off','vue/require-prop-types': 'off','vue/singleline-html-element-content-newline': 'off','vue/multiline-html-element-content-newline': 'off','vue/max-attributes-per-line': ['error',{singleline: 5,multiline: {max: 1,allowFirstLine: false}}],'vue/html-indent': ['error',2,{// attribute: 1,// baseIndent: 1,// closeBracket: 0,// alignAttributesVertically: true,ignores: ['ConditionalExpression']}],'vue/html-closing-bracket-newline': ['error',{singleline: 'never',multiline: 'always'}],'vue/html-self-closing': ['error',{html: {void: 'always',normal: 'always',component: 'always'},svg: 'always',math: 'always'}]},overrides: [{files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'],env: {jest: true}},{files: ['*.vue'],rules: {indent: 'off','vue/script-indent': ['error',2,{baseIndent: 1,switchCase: 1,ignores: []}]}}],globals: {// 全局变量defineProps: 'readonly',defineEmits: 'readonly',defineExpose: 'readonly',withDefaults: 'readonly'}
}
3.配置.prettierrc.js
module.exports = {// 一行最多 100 字符printWidth: 100,// 使用 4 个空格缩进tabWidth: 2,// 不使用缩进符,而使用空格useTabs: false,// 行尾需要有分号semi: false,// 使用单引号singleQuote: true,// 对象的 key 仅在必要时用引号quoteProps: 'as-needed',// jsx 使用单引号,而使用双引号jsxSingleQuote: true,// 末尾逗号:es5(在ES5中有效的结尾逗号),末尾不需要逗号'none'trailingComma: 'none',// 大括号内的首尾需要空格bracketSpacing: true,// 将多行HTML(HTML、JSX、Vue、Angular)元素的 > 放在最后一行的末尾jsxBracketSameLine: false,// 箭头函数,只有一个参数的时候,也需要括号:always;avoid(尽可能省略)arrowParens: 'always',// 每个文件格式化的范围是文件的全部内容rangeStart: 0,rangeEnd: Infinity,// 不需要写文件开头的 @prettierrequirePragma: false,// 不需要自动在文件开头插入 @prettierinsertPragma: false,// 使用默认的折行标准proseWrap: 'preserve',// 根据显示样式决定 html 要不要折行htmlWhitespaceSensitivity: 'css',// Vue文件脚本和样式标签缩进vueIndentScriptAndStyle: false,// 换行符使用 lf(Linux风格)endOfLine: 'lf',// 单个属性换行singleAttributePerLine: false,// 默认就是 falsehtmlClosingBracketNewline: false,//函数括号前空格spaceBeforeFunctionParen: false,// vue文件中的script和style的内容缩进vueIndentScriptAndStyle: true,
};
4.创建.prettierignore
# 依赖目录
node_modules/
dist/
build/# 配置文件
*.config.js
.eslintrc.js
.prettierrc.js# 日志文件
*.log# 运行时文件
coverage/
.nyc_output/# 环境文件
.env
.env.*
!.env.example# IDE 文件
.vscode/
.idea/# 操作系统文件
.DS_Store
Thumbs.db# 其他
public/
temp/
*.min.js
*.min.css# 特定文件
package-lock.json
yarn.lock# 测试文件
**/__tests__/
**/*.spec.js
**/*.test.js

九.scss样式调用

1.vue.config.js 中的 SCSS 配置
const { defineConfig } = require('@vue/cli-service')
const path = require('path')function resolve(dir) {return path.join(__dirname, dir)
}module.exports = defineConfig({transpileDependencies: true,css: {loaderOptions: {sass: {// 全局引入变量和混入additionalData: `@import "@/styles/variables.scss";@import "@/styles/mixins.scss";`},scss: {additionalData: `@import "@/styles/variables.scss";@import "@/styles/mixins.scss";`}}},chainWebpack(config) {// 其他配置...}
})

注意:全局引入变量和混入,其他地方就不需要引入了

2. 在 main.js 中引入全局样式
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'// 引入全局样式
import '@/styles/index.scss'import '@/icons' // icon
import '@/permission' // permission controlimport ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css' 
// if (process.env.NODE_ENV === 'production') {
const { mockXHR } = require('../mock')
mockXHR()
// }
Vue.use(ElementUI)Vue.config.productionTip = falseconst app = new Vue({el: '#app',router,store,render: (h) => h(App)
})// 这样你还可以在其他地方引用这个实例
export default app
3.更新src/styles/index.scss文件
// 引入基础样式
@import '~normalize.css/normalize.css';// 引入变量和混入
@import 'variables';
@import 'mixins';// 引入组件样式
@import 'transition';
@import 'element-ui';
@import 'sidebar';// 其他不变
4.更新src/styles/variables.scss(全局变量)
$menuText: #ffffff;        // 菜单默认文字颜色(白色)
$menuActiveText: #ffffff;  // 选中菜单文字颜色
$subMenuActiveText: #ffffff; // 子菜单选中文字颜色$menuBg: #a60404;          // 主菜单背景色
$menuHover: #960a1b;       // 菜单悬停色(比主色稍深)
$subMenuBg: #c50319;       // 子菜单背景色(可稍暗)
$subMenuHover: #8a000f;    // 子菜单悬停色
$menuActive:linear-gradient(90deg, #fc9a39, #f95544);$sideBarWidth: 210px;// 导出 CSS 变量
:root {--menu-text: #{$menuText};--menu-active-text: #{$menuActiveText};--submenu-active-text: #{$subMenuActiveText};--menu-bg: #{$menuBg};--menu-hover: #{$menuHover};--submenu-bg: #{$subMenuBg};--submenu-hover: #{$subMenuHover};--side-bar-width: #{$sideBarWidth};
}

5.更新src\layout\components\Sidebar\index.vue

<template><div :class="{ 'has-logo': showLogo }"><logo v-if="showLogo" :collapse="isCollapse" /><el-scrollbar wrap-class="scrollbar-wrapper"><el-menu:default-active="activeMenu":collapse="isCollapse":background-color="'var(--menu-bg)'":text-color="'var(--menu-text)'":unique-opened="false":active-text-color="'var(--menu-active-text)'":collapse-transition="false"mode="vertical"><sidebar-itemv-for="route in routes":key="route.path":item="route":base-path="route.path"/></el-menu></el-scrollbar></div>
</template><script>import { mapGetters } from 'vuex'import Logo from './Logo'import SidebarItem from './SidebarItem'export default {components: { SidebarItem, Logo },computed: {...mapGetters(['sidebar']),routes() {return this.$router.options.routes},activeMenu() {const route = this.$routeconst { meta, path } = route// if set path, the sidebar will highlight the path you setif (meta.activeMenu) {return meta.activeMenu}return path},showLogo() {return this.$store.state.settings.sidebarLogo},// variables() {//   console.log(111111)//   return menu// },isCollapse() {return !this.sidebar.opened}}}
</script>

package.json

{"name": "vue-admin-template","version": "5.0.0","description": "A vue admin template with Vue CLI 5","scripts": {"dev": "vue-cli-service serve","build:prod": "vue-cli-service build","build:stage": "vue-cli-service build --mode staging","preview": "node build/index.js --preview","lint": "eslint --ext .js,.vue src","test:unit": "vue-cli-service test:unit","test:e2e": "vue-cli-service test:e2e"},"dependencies": {"axios": "^1.4.0","element-ui": "^2.15.13","js-cookie": "^3.0.1","normalize.css": "^8.0.1","nprogress": "^0.2.0","vue": "^2.7.14","vue-count-to": "^1.0.13","vue-router": "^3.6.5","vuex": "^3.6.2"},"devDependencies": {"@babel/core": "^7.22.5","@babel/preset-env": "^7.22.5","@vue/cli-plugin-babel": "^5.0.8","@vue/cli-plugin-e2e-cypress": "^5.0.8","@vue/cli-plugin-eslint": "^5.0.8","@vue/cli-plugin-unit-jest": "^5.0.8","@vue/cli-service": "^5.0.8","@vue/eslint-config-standard": "6.1.0","@vue/test-utils": "^1.3.3","babel-eslint": "^10.1.0","babel-plugin-component": "^1.1.1","body-parser": "1.20.2","buffer": "^6.0.3","chalk": "^4.1.2","chokidar": "3.5.3","compression-webpack-plugin": "^10.0.0","connect": "^3.7.0","crypto-browserify": "^3.12.1","eslint": "^7.32.0","eslint-config-standard": "16.0.3","eslint-plugin-import": "2.27.5","eslint-plugin-node": "11.1.0","eslint-plugin-promise": "5.2.0","eslint-plugin-vue": "^7.20.0","file-loader": "6.2.0","mockjs": "^1.1.0","path-browserify": "^1.0.1","process": "^0.11.10","runjs": "^4.4.2","sass": "1.66.0","sass-loader": "13.3.0","script-ext-html-webpack-plugin": "^2.1.5","stream-browserify": "^3.0.0","svg-sprite-loader": "6.0.11","svgo": "1.3.2","svgo-loader": "3.0.0","url-loader": "4.1.1","vue-template-compiler": "^2.7.14","webpack-bundle-analyzer": "^4.8.0"},"engines": {"node": ">=14.0.0","npm": ">=6.0.0"},"browserslist": ["> 1%","last 2 versions","not dead"]
}

vue.config.js文件如下

const { defineConfig } = require('@vue/cli-service')
const path = require('path')
const webpack = require('webpack')function resolve(dir) {return path.join(__dirname, dir)
}module.exports = defineConfig({transpileDependencies: true,lintOnSave: process.env.NODE_ENV === 'development',productionSourceMap: false,configureWebpack: {resolve: {alias: {'@': resolve('src')// // 添加其他常用别名// components: resolve('src/components'),// views: resolve('src/views'),// utils: resolve('src/utils'),// api: resolve('src/api')},// 添加 polyfill 配置fallback: {path: require.resolve('path-browserify'),crypto: require.resolve('crypto-browserify'),stream: require.resolve('stream-browserify'),buffer: require.resolve('buffer/'),// util: require.resolve('util/'),process: require.resolve('process/browser')}},plugins: [new webpack.ProvidePlugin({'window.Quill': 'quill/dist/quill.js',Quill: 'quill/dist/quill.js',// 提供全局的 process 和 Bufferprocess: 'process/browser',Buffer: ['buffer', 'Buffer']})]},chainWebpack(config) {config.plugins.delete('preload')config.plugins.delete('prefetch')config.module.rule('svg').exclude.add(resolve('src/icons')).end()config.module.rule('icons').test(/\.svg$/).include.add(resolve('src/icons')).end().use('svg-sprite-loader').loader('svg-sprite-loader').options({symbolId: 'icon-[name]'}).end()// 添加 svgo-loader 优化 SVG.use('svgo-loader').loader('svgo-loader').options({plugins: [{ name: 'removeAttrs', params: { attrs: '(fill|stroke)' } },{ name: 'removeTitle', active: true },{ name: 'removeDimensions', active: true }]}).end()// // 添加字体文件处理规则// config.module// .rule('fonts')// .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/)// .use('url-loader')// .loader('url-loader')// .options({//  limit: 10000,//  name: 'fonts/[name].[hash:8].[ext]'//  })// .end()config.when(process.env.NODE_ENV !== 'development', (config) => {config.plugin('ScriptExtHtmlWebpackPlugin').after('html').use('script-ext-html-webpack-plugin', [{inline: /runtime\..*\.js$/}]).end()config.optimization.splitChunks({chunks: 'all',cacheGroups: {libs: {name: 'chunk-libs',test: /[\\/]node_modules[\\/]/,priority: 10,chunks: 'initial'},elementUI: {name: 'chunk-elementUI',priority: 20,test: /[\\/]node_modules[\\/]_?element-ui(.*)/},commons: {name: 'chunk-commons',test: resolve('src/components'),minChunks: 3,priority: 5,reuseExistingChunk: true}}})config.optimization.runtimeChunk('single')})},// 添加 CSS 相关配置css: {loaderOptions: {sass: {// 使用新的 Sass APIimplementation: require('sass'),// 全局注入变量和 mixinadditionalData: `@import "~@/styles/variables.scss";@import "~@/styles/mixin.scss";`},scss: {implementation: require('sass'),additionalData: `@import "~@/styles/variables.scss";@import "~@/styles/mixin.scss";`}}},devServer: {port: 9527,open: true,hot: true,client: {overlay: {warnings: false,errors: true}},// 使用 setupMiddlewares 替代 onBeforeSetupMiddlewaresetupMiddlewares: (middlewares, devServer) => {if (!devServer) {throw new Error('webpack-dev-server is not defined')}// 引入并设置 mock serverconst mockServer = require('./mock/mock-server')mockServer(devServer.app)return middlewares},proxy: {'/api': {target: 'http://localhost:8080',changeOrigin: true,pathRewrite: {'^/api': ''}}}}
})

十.按需加载elementui

1. 安装必要的依赖
npm install element-ui@2.15.13 -S
npm install babel-plugin-component@1.1.1 -D
npm install sass@1.32.13 -D
npm install sass-loader@^10.2.0 -D
2. 修改 babel.config.js
module.exports = {presets: ['@vue/cli-plugin-babel/preset'],plugins: [['component',{libraryName: 'element-ui',styleLibraryName: 'theme-chalk'}]]
}
3. 创建 Element UI 按需加载配置文件

src/components/element.js

import Vue from 'vue'
import 'element-ui/lib/theme-chalk/index.css' // 全局引入样式// 按需引入组件
import {// 布局组件Container,Header,Aside,Main,Footer,// 基础组件Button,Input,Form,FormItem,Select,Option,OptionGroup,Checkbox,CheckboxGroup,Radio,RadioGroup,RadioButton,Switch,DatePicker,TimePicker,TimeSelect,// 导航组件Menu,Submenu,MenuItem,MenuItemGroup,Breadcrumb,BreadcrumbItem,Dropdown,DropdownMenu,DropdownItem,Steps,Step,Tabs,TabPane,// 通知组件Alert,Loading,Message,MessageBox,Notification,// 其他组件Dialog,Tooltip,Popover,Card,Carousel,CarouselItem,Collapse,CollapseItem,Pagination,Table,TableColumn,Tag,Progress,Tree,Upload
} from 'element-ui'// 注册组件
const components = [Container,Header,Aside,Main,Footer,Button,Input,Form,FormItem,Select,Option,OptionGroup,Checkbox,CheckboxGroup,Radio,RadioGroup,RadioButton,Switch,DatePicker,TimePicker,TimeSelect,Menu,Submenu,MenuItem,MenuItemGroup,Breadcrumb,BreadcrumbItem,Dropdown,DropdownMenu,DropdownItem,Steps,Step,Tabs,TabPane,Alert,Dialog,Tooltip,Popover,Card,Carousel,CarouselItem,Collapse,CollapseItem,Pagination,Table,TableColumn,Tag,Progress,Tree,Upload
]components.forEach(component => {Vue.component(component.name, component)
})// 注册服务
Vue.use(Loading.directive)Vue.prototype.$loading = Loading.service
Vue.prototype.$msgbox = MessageBox
Vue.prototype.$alert = MessageBox.alert
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$prompt = MessageBox.prompt
Vue.prototype.$notify = Notification
Vue.prototype.$message = Messageexport default {install() {// 空实现,用于统一导出}
}
4. 4. 在 main.js 中引入 Element UI
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'// 引入全局样式
import '@/styles/index.scss'// 引入 Element UI
import './plugins/element'// 引入图标
import '@/icons'// 引入权限控制
import '@/permission'Vue.config.productionTip = falsenew Vue({el: '#app',router,store,render: h => h(App)
})
5. 更新 vue.config.js 配置

src/styles/element-custom.scss

const { defineConfig } = require('@vue/cli-service')
const path = require('path')function resolve(dir) {return path.join(__dirname, dir)
}module.exports = defineConfig({transpileDependencies: true,lintOnSave: process.env.NODE_ENV === 'development',productionSourceMap: false,configureWebpack: {resolve: {alias: {'@': resolve('src'),'components': resolve('src/components'),'views': resolve('src/views'),'utils': resolve('src/utils'),'api': resolve('src/api')},fallback: {"path": require.resolve("path-browserify"),"crypto": require.resolve("crypto-browserify"),"stream": require.resolve("stream-browserify"),"buffer": require.resolve("buffer/"),"util": require.resolve("util/"),"process": require.resolve("process/browser")}},plugins: [new webpack.ProvidePlugin({process: 'process/browser',Buffer: ['buffer', 'Buffer']})]},// CSS 配置css: {loaderOptions: {scss: {additionalData: `@import "@/styles/variables.scss";@import "@/styles/mixins.scss";`},sass: {additionalData: `@import "@/styles/variables.scss"@import "@/styles/mixins.scss"`}}},chainWebpack(config) {config.plugins.delete('preload')config.plugins.delete('prefetch')config.module.rule('svg').exclude.add(resolve('src/icons')).end()config.module.rule('icons').test(/\.svg$/).include.add(resolve('src/icons')).end().use('svg-sprite-loader').loader('svg-sprite-loader').options({symbolId: 'icon-[name]'}).end()config.when(process.env.NODE_ENV !== 'development',config => {config.plugin('ScriptExtHtmlWebpackPlugin').after('html').use('script-ext-html-webpack-plugin', [{inline: /runtime\..*\.js$/}]).end()config.optimization.splitChunks({chunks: 'all',cacheGroups: {libs: {name: 'chunk-libs',test: /[\\/]node_modules[\\/]/,priority: 10,chunks: 'initial'},elementUI: {name: 'chunk-elementUI',priority: 20,test: /[\\/]node_modules[\\/]_?element-ui(.*)/},commons: {name: 'chunk-commons',test: resolve('src/components'),minChunks: 3,priority: 5,reuseExistingChunk: true}}})config.optimization.runtimeChunk('single')})},devServer: {port: 9527,open: true,hot: true,client: {overlay: {warnings: false,errors: true}},proxy: {'/api': {target: 'http://localhost:8080',changeOrigin: true,pathRewrite: {'^/api': ''}}}}
})

十一自定义element主题(不安装主题生成工具)

1. 创建自定义主题文件

src/styles/element-custom.scss

// Element UI 主题变量覆盖
$--color-primary: #1890ff; // 主色 - 蓝色
$--color-success: #52c41a; // 成功色 - 绿色
$--color-warning: #faad14; // 警告色 - 橙色
$--color-danger: #f5222d;  // 危险色 - 红色
$--color-info: #909399;    // 信息色 - 灰色// 文本颜色
$--color-text-primary: #262626;
$--color-text-regular: #595959;
$--color-text-secondary: #8c8c8c;
$--color-text-placeholder: #bfbfbf;// 边框颜色
$--border-color-base: #d9d9d9;
$--border-color-light: #e8e8e8;
$--border-color-lighter: #f0f0f0;
$--border-color-extra-light: #f5f5f5;// 背景颜色
$--background-color-base: #f5f5f5;// 字体
$--font-path: '~element-ui/lib/theme-chalk/fonts';// 按钮
$--button-font-weight: 400;
$--button-border-radius: 4px;
$--button-padding-vertical: 8px;
$--button-padding-horizontal: 16px;// 输入框
$--input-height: 36px;
$--input-border-radius: 4px;
$--input-border-color: $--border-color-base;
$--input-focus-border-color: $--color-primary;// 表格
$--table-border-color: $--border-color-light;
$--table-header-background: #fafafa;
$--table-row-hover-background: #f5f5f5;// 菜单
$--menu-item-height: 44px;
$--menu-item-font-size: 14px;// 弹窗
$--dialog-padding-primary: 20px;
$--dialog-border-radius: 6px;// 阴影
$--box-shadow-light: 0 2px 8px rgba(0, 0, 0, 0.15);
$--box-shadow-base: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);// 导入 Element UI 默认变量(用于引用其他变量)
@import "~element-ui/packages/theme-chalk/src/common/var.scss";// 自定义变量
$--custom-sidebar-width: 220px;
$--custom-sidebar-bg: #001529;
$--custom-header-height: 60px;
2. 创建自定义主题样式文件
src/styles/element-custom.scss
// 引入自定义变量
@import 'element-variables';// 覆盖 Element UI 样式
.el-button {// 按钮样式覆盖&.el-button--primary {background: linear-gradient(45deg, $--color-primary, #40a9ff);border: none;&:hover,&:focus {background: linear-gradient(45deg, #40a9ff, $--color-primary);border: none;}&:active {background: linear-gradient(45deg, darken($--color-primary, 10%), darken(#40a9ff, 10%));}}// 圆角按钮&.is-round {border-radius: 20px;}
}.el-input {// 输入框样式.el-input__inner {border-radius: $--input-border-radius;border: 1px solid $--input-border-color;transition: all 0.3s ease;&:focus {border-color: $--input-focus-border-color;box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);}}// 输入框前缀和后缀.el-input-group__prepend,.el-input-group__append {background: $--background-color-base;border: 1px solid $--border-color-base;}
}.el-table {// 表格样式border-radius: 6px;overflow: hidden;th {background: $--table-header-background;color: $--color-text-primary;font-weight: 500;}td {border-bottom: 1px solid $--table-border-color;}.el-table__row:hover {background: $--table-row-hover-background;}
}.el-dialog {// 弹窗样式border-radius: $--dialog-border-radius;.el-dialog__header {padding: $--dialog-padding-primary;border-bottom: 1px solid $--border-color-light;background: #fafafa;.el-dialog__title {color: $--color-text-primary;font-weight: 500;}}.el-dialog__body {padding: $--dialog-padding-primary;}.el-dialog__footer {padding: $--dialog-padding-primary;border-top: 1px solid $--border-color-light;text-align: right;}
}.el-menu {// 菜单样式border: none;.el-menu-item,.el-submenu__title {height: $--menu-item-height;line-height: $--menu-item-height;&:hover {background: rgba($--color-primary, 0.1);}&.is-active {color: $--color-primary;background: rgba($--color-primary, 0.1);border-right: 3px solid $--color-primary;}}
}.el-pagination {// 分页样式.btn-prev,.btn-next,.number {border-radius: 4px;margin: 0 4px;&:hover {color: $--color-primary;}}.number.active {background: $--color-primary;border-color: $--color-primary;color: #fff;}
}.el-tag {// 标签样式border-radius: 12px;border: none;&.el-tag--primary {background: rgba($--color-primary, 0.1);color: $--color-primary;}&.el-tag--success {background: rgba($--color-success, 0.1);color: $--color-success;}&.el-tag--warning {background: rgba($--color-warning, 0.1);color: $--color-warning;}&.el-tag--danger {background: rgba($--color-danger, 0.1);color: $--color-danger;}
}// 卡片样式
.el-card {border-radius: 6px;border: 1px solid $--border-color-light;.el-card__header {padding: 16px 20px;border-bottom: 1px solid $--border-color-light;background: #fafafa;font-weight: 500;}.el-card__body {padding: 20px;}
}// 消息提示样式
.el-message {min-width: auto;padding: 10px 16px;border-radius: 4px;box-shadow: $--box-shadow-light;&__icon {font-size: 16px;}
}// 加载样式
.el-loading-mask {background: rgba(255, 255, 255, 0.9);
}// 自定义暗色主题类
.el-dark-theme {.el-table {th {background: #1f1f1f;color: #fff;}.el-table__row:hover {background: #2a2a2a;}}.el-card {background: #1f1f1f;border-color: #333;.el-card__header {background: #2a2a2a;border-color: #333;color: #fff;}}
}// 响应式调整
@media (max-width: 768px) {.el-dialog {width: 90% !important;margin: 5% auto !important;}.el-table {.el-table__cell {padding: 8px 4px;}}
}

3. 更新 index.scss 引入自定义主题

// 引入基础样式
@import '~normalize.css/normalize.css';// 引入 Element UI 默认样式(必须在前)
@import '~element-ui/lib/theme-chalk/index.css';// 引入变量和混入
@import 'variables';
@import 'mixins';// 引入自定义 Element UI 主题
@import 'element-variables';
@import 'element-custom';// 引入组件样式
@import 'transition';
@import 'sidebar';
@import 'form-components';// 全局样式
* {box-sizing: border-box;margin: 0;padding: 0;
}html {height: 100%;box-sizing: border-box;
}body {height: 100%;margin: 0;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB','Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size: 14px;line-height: 1.5;color: $--color-text-primary;background-color: $--background-color-base;-moz-osx-font-smoothing: grayscale;-webkit-font-smoothing: antialiased;
}#app {height: 100%;
}// 其他全局样式...

4. 创建主题切换工具

src/utils/theme.js

/*** Element UI 主题工具类*/class ThemeUtils {// 主题配置static themes = {light: {'--el-color-primary': '#1890ff','--el-color-success': '#52c41a','--el-color-warning': '#faad14','--el-color-danger': '#f5222d','--el-bg-color': '#ffffff'},dark: {'--el-color-primary': '#177ddc','--el-color-success': '#49aa19','--el-color-warning': '#d89614','--el-color-danger': '#a61d24','--el-bg-color': '#141414'},blue: {'--el-color-primary': '#0960bd','--el-color-success': '#52c41a','--el-color-warning': '#faad14','--el-color-danger': '#f5222d','--el-bg-color': '#f0f2f5'}}// 应用主题static applyTheme(themeName) {const theme = this.themes[themeName] || this.themes.lightconst root = document.documentElementObject.entries(theme).forEach(([key, value]) => {root.style.setProperty(key, value)})// 添加主题类名root.classList.remove('theme-light', 'theme-dark', 'theme-blue')root.classList.add(`theme-${themeName}`)// 保存到 localStoragelocalStorage.setItem('element-theme', themeName)}// 获取当前主题static getCurrentTheme() {return localStorage.getItem('element-theme') || 'light'}// 初始化主题static initTheme() {const savedTheme = this.getCurrentTheme()this.applyTheme(savedTheme)return savedTheme}// 切换主题static toggleTheme() {const current = this.getCurrentTheme()const themes = Object.keys(this.themes)const currentIndex = themes.indexOf(current)const nextIndex = (currentIndex + 1) % themes.lengthconst nextTheme = themes[nextIndex]this.applyTheme(nextTheme)return nextTheme}// 更新自定义颜色static updateCustomColor(type, color) {const root = document.documentElementroot.style.setProperty(`--el-color-${type}`, color)// 保存自定义颜色const customColors = JSON.parse(localStorage.getItem('element-custom-colors') || '{}')customColors[type] = colorlocalStorage.setItem('element-custom-colors', JSON.stringify(customColors))}
}export default ThemeUtils

5. 创建主题切换组件

src/components/ThemeSwitch.vue

<template><el-tooltip :content="`当前主题: ${currentTheme}`" placement="bottom"><el-button:icon="themeIcon"circle@click="toggleTheme"class="theme-switch"/></el-tooltip>
</template><script>
import ThemeUtils from '@/utils/theme'export default {name: 'ThemeSwitch',data() {return {currentTheme: 'light'}},computed: {themeIcon() {const icons = {light: 'el-icon-sunny',dark: 'el-icon-moon',blue: 'el-icon-umbrella'}return icons[this.currentTheme] || 'el-icon-sunny'}},mounted() {this.currentTheme = ThemeUtils.initTheme()},methods: {toggleTheme() {this.currentTheme = ThemeUtils.toggleTheme()this.$message.success(`已切换到 ${this.currentTheme} 主题`)}}
}
</script><style lang="scss" scoped>
.theme-switch {border: none;background: var(--el-bg-color);box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);transition: all 0.3s ease;&:hover {transform: rotate(30deg);box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);}i {color: var(--el-color-primary);}
}
</style>

6. 在 main.js 中初始化主题

import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'// 引入全局样式
import '@/styles/index.scss'// 引入 Element UI
import './plugins/element'// 引入图标
import '@/icons'// 引入权限控制
import '@/permission'// 初始化主题
import ThemeUtils from '@/utils/theme'
ThemeUtils.initTheme()Vue.config.productionTip = falsenew Vue({el: '#app',router,store,render: h => h(App)
})

7. 在布局中使用主题切换

<template><el-container class="layout-container"><el-header class="header"><div class="left"><h1>Vue Admin Template</h1></div><div class="right"><theme-switch /></div></el-header><el-container><el-aside width="200px"><!-- 侧边栏内容 --></el-aside><el-main><!-- 主要内容 --></el-main></el-container></el-container>
</template><script>
import ThemeSwitch from '@/components/ThemeSwitch'export default {name: 'Layout',components: {ThemeSwitch}
}
</script><style lang="scss" scoped>
.layout-container {height: 100vh;

8、修改sidebar.scss

#app {.main-container {min-height: 100%;transition: margin-left .28s;margin-left: $sideBarWidth;position: relative;}.sidebar-container {transition: width 0.28s;width: $sideBarWidth !important;background-color: var(--menu-bg);height: 100%;position: fixed;font-size: 0px;top: 0;bottom: 0;left: 0;z-index: 1001;overflow: hidden;// reset element-ui css.horizontal-collapse-transition {transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;}.scrollbar-wrapper {overflow-x: hidden !important;}.el-scrollbar__bar.is-vertical {right: 0px;}.el-scrollbar {height: 100%;}&.has-logo {.el-scrollbar {height: calc(100% - 50px);}}.is-horizontal {display: none;}a {display: inline-block;width: 100%;overflow: hidden;}.svg-icon {margin-right: 16px;}.sub-el-icon {margin-right: 12px;margin-left: -2px;}.el-menu {border: none;height: 100%;width: 100% !important;}// menu hover.submenu-title-noDropdown,.el-submenu__title {&:hover {background-color: var(--menu-hover) !important;}}.is-active>.el-submenu__title {color: var(--sub-menu-active-text) !important;}& .nest-menu .el-submenu>.el-submenu__title,& .el-submenu .el-menu-item {min-width: $sideBarWidth !important;background-color: var(--sub-menu-bg) !important;&:hover {background-color: var(--sub-menu-hover) !important;}}}.hideSidebar {.sidebar-container {width: 54px !important;}.main-container {margin-left: 54px;}.submenu-title-noDropdown {padding: 0 !important;position: relative;.el-tooltip {padding: 0 !important;.svg-icon {margin-left: 20px;}.sub-el-icon {margin-left: 19px;}}}.el-submenu {overflow: hidden;&>.el-submenu__title {padding: 0 !important;.svg-icon {margin-left: 20px;}.sub-el-icon {margin-left: 19px;}.el-submenu__icon-arrow {display: none;}}}.el-menu--collapse {.el-submenu {&>.el-submenu__title {&>span {height: 0;width: 0;overflow: hidden;visibility: hidden;display: inline-block;}}}}}.el-menu--collapse .el-menu .el-submenu {min-width: $sideBarWidth !important;}// mobile responsive.mobile {.main-container {margin-left: 0px;}.sidebar-container {transition: transform .28s;width: $sideBarWidth !important;}&.hideSidebar {.sidebar-container {pointer-events: none;transition-duration: 0.3s;transform: translate3d(-$sideBarWidth, 0, 0);}}}.withoutAnimation {.main-container,.sidebar-container {transition: none;}}
}// when menu collapsed
.el-menu--vertical {&>.el-menu {.svg-icon {margin-right: 16px;}.sub-el-icon {margin-right: 12px;margin-left: -2px;}}.nest-menu .el-submenu>.el-submenu__title,.el-menu-item {&:hover {// you can use $subMenuHoverbackground-color: var(--menu-hover) !important;}}// the scroll bar appears when the subMenu is too long>.el-menu--popup {max-height: 100vh;overflow-y: auto;&::-webkit-scrollbar-track-piece {background: #d3dce6;}&::-webkit-scrollbar {width: 6px;}&::-webkit-scrollbar-thumb {background: #99a9bf;border-radius: 20px;}}
}

其他细节部分不在此表述

http://www.dtcms.com/a/350674.html

相关文章:

  • C语言中哪些常见的坑
  • Linux的奇妙冒险———进程信号
  • 滲透測試工具
  • Microsoft 365 中的 Rules-Based Classification 功能深度解析:企业数据治理与合规的智能基石
  • 25年8月通信基础知识补充2:星座的峭度(Kurtosis)、ISAC
  • 朴素贝叶斯分类器
  • A股市场高级日历效应详解与实战指南
  • 【P2P】P2P主要技术及RELAY服务1:python实现
  • 【Git】fatal: Unable to create ‘.git/index.lock’: File exists.
  • 迁移面试题
  • 亚远景- 从算法到刹车片:ISO/PAS 8800如何量化自动驾驶的“安全冗余”?
  • Life:Internship in OnSea Day 64
  • PyTorch损失函数全解析与实战指南
  • 高性能C++实践:原子操作与无锁队列实现
  • C++ #pragma
  • C++初阶(3)C++入门基础2
  • 现代C++工具链实战:CMake + Conan + vcpkg依赖管理
  • MYSQL的bin log是什么
  • JUC并发编程08 - 同步模式/异步模式
  • ROS2 python功能包launch,config文件编译后找不到
  • 链表OJ习题(2)
  • 搭建基于LangChain实现复杂RAG聊天机器人
  • AI在软件研发流程中的提效案例
  • 在vue3后台项目中使用热力图,并给热力图增加点击选中事件
  • Java中删除字符串首字符
  • 【51单片机】【protues仿真】基于51单片机数码管温度报警器系统
  • AR眼镜赋能水利智能巡检的创新实践
  • 算法题打卡力扣第167题:两数之和——输入有序数组(mid)
  • VASP计算层错能(SFE)全攻略2
  • python自学笔记12 NumPy 常见运算