├── .editorconfig ├── .gitignore ├── README.en.md ├── README.md ├── index.html ├── package.json ├── prettier.config.js ├── public └── favicon.ico ├── src ├── App.vue ├── assets │ ├── css │ │ ├── common.scss │ │ ├── custom.scss │ │ ├── layout.scss │ │ ├── mixin.scss │ │ └── themes.scss │ ├── img │ │ └── user.jpg │ ├── logo.png │ └── svg │ │ ├── full-screen-max.svg │ │ ├── full-screen-min.svg │ │ └── language.svg ├── components │ ├── SvgIcon.vue │ └── layout │ │ ├── Layout.vue │ │ ├── components │ │ ├── Logo.vue │ │ ├── layout.js │ │ ├── navbar │ │ │ ├── FullScreen.vue │ │ │ ├── UserInfo.vue │ │ │ ├── fullScreen.js │ │ │ └── index.vue │ │ ├── setting │ │ │ ├── index.js │ │ │ └── index.vue │ │ ├── sidebar │ │ │ ├── SidebarItem.vue │ │ │ └── index.vue │ │ └── tabs │ │ │ └── index.vue │ │ └── index.vue ├── main.js ├── router │ └── index.js ├── store │ └── index.js ├── utils │ ├── storage.js │ └── svg-loader.js └── views │ └── Home.vue └── vite.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | # 编辑器代码格式规范 VSCode需要插件支持 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | # 避免 warning: LF will be replaced by CRLF: 10 | # $: git config --global core.autocrlf false 11 | end_of_line = lf 12 | insert_final_newline = true # 去除行首任意空白字符 13 | trim_trailing_whitespace = true # 始终在文件末尾插入一个新行 14 | 15 | [*.md] 16 | max_line_length = off 17 | insert_final_newline = false 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 |
2 |

vue3-admin-vite

3 |
4 | 5 | **English** | [中文](./README.md) 6 | 7 | ## Introduction 8 | 9 | vue3-admin-vite is a free and open source back-end template. Using the latest`vue3.x`,`vite2.x`,`Element Plus` and other mainstream technology development, the out-of-the-box back-end front-end solutions can also be used for learning reference. 10 | 11 | ## Feature 12 | 13 | - **State of The Art Development**:Use front-end front-end technology development such as Vue3/vite2 14 | - **Theming**: Configurable themes, custom themes, multiple layouts 15 | 16 | ## Preview 17 | 18 | - Online preview address: [vue3-admin-vite] 19 | 20 |

21 | vue3-admin-demo 22 | vue3-admin-demo 23 | vue3-admin-demo 24 | vue3-admin-demo 25 |

26 | 27 | ## Preparation 28 | 29 | - [node](http://nodejs.org/) and [git](https://git-scm.com/) - Project development environment 30 | - [Vite](https://vitejs.dev/) - Familiar with vite features 31 | - [Vue3](https://v3.vuejs.org/) - Familiar with Vue basic syntax 32 | - [Es6+](http://es6.ruanyifeng.com/) - Familiar with es6 basic syntax 33 | - [element-Plus](https://element-plus.org/) - ui basic use 34 | 35 | ## Install and use 36 | 37 | - Get the project code 38 | 39 | ```bash 40 | git clone https://github.com/wuufeii/vue3-admin-vite.git 41 | ``` 42 | 43 | - Installation dependencies 44 | 45 | ```bash 46 | cd vue3-admin-vite 47 | 48 | npm install 49 | 50 | ``` 51 | 52 | - run 53 | 54 | ```bash 55 | npm run dev 56 | ``` 57 | 58 | - build 59 | 60 | ```bash 61 | npm run build 62 | ``` 63 | 64 | ## How to contribute 65 | 66 | You are very welcome to join![Raise an issue](https://github.com/wuufeii/vue3-admin-vite/issues/new/choose) Or submit a Pull Request。 67 | 68 | **Pull Request:** 69 | 70 | 1. Fork code! 71 | 2. Create your own branch: `git checkout -b feat/xxxx` 72 | 3. Submit your changes: `git commit -am 'feat(function): add xxxxx'` 73 | 4. Push your branch: `git push origin feat/xxxx` 74 | 5. submit`pull request` 75 | 76 | ## Git Contribution submission specification 77 | 78 | - `feat` Add new features 79 | - `fix` Fix the problem/BUG 80 | - `style` The code style is related and does not affect the running result 81 | - `perf` Optimization/performance improvement 82 | - `refactor` Refactor 83 | - `revert` Undo edit 84 | - `test` Test related 85 | - `docs` Documentation/notes 86 | - `chore` Dependency update/scaffolding configuration modification etc. 87 | - `workflow` Workflow improvements 88 | - `ci` Continuous integration 89 | - `types` Type definition file changes 90 | - `wip` In development 91 | 92 | ## Browser support 93 | 94 | The `Chrome 80+` browser is recommended for local development 95 | 96 | Support modern browsers, not IE 97 | 98 | | [ Edge](http://godban.github.io/browsers-support-badges/)
IE | [ Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | 99 | | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | 100 | | not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

vue3-admin-vite

3 |
4 | 5 | **中文** | [English](./README.en.md) 6 | 7 | ## 简介 8 | 9 | vue3-admin-vite 是一个免费开源的后台模版。使用了最新的`vue3.x`,`vite2.x`,`Element Plus`等主流技术开发,开箱即用的后台前端解决方案,也可用于学习参考。 10 | 11 | ## 特性 12 | 13 | - **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发 14 | - **主题**:可配置的主题,自定义主题,多样布局 15 | 16 | ## 预览 17 | 18 | - 在线预览地址: [vue3-admin-vite] 19 | 20 |

21 | vue3-admin-demo 22 | vue3-admin-demo 23 | vue3-admin-demo 24 | vue3-admin-demo 25 |

26 | 27 | ## 准备 28 | 29 | - [node](http://nodejs.org/) 和 [git](https://git-scm.com/) -项目开发环境 30 | - [Vite](https://vitejs.dev/) - 熟悉 vite 特性 31 | - [Vue3](https://v3.vuejs.org/) - 熟悉 Vue 基础语法 32 | - [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法 33 | - [element-Plus](https://element-plus.org/) - ui 基本使用 34 | 35 | ## 安装使用 36 | 37 | - 获取项目代码 38 | 39 | ```bash 40 | git clone https://github.com/wuufeii/vue3-admin-vite.git 41 | ``` 42 | 43 | - 安装依赖 44 | 45 | ```bash 46 | cd vue3-admin-vite 47 | 48 | npm install 49 | 50 | ``` 51 | 52 | - 运行 53 | 54 | ```bash 55 | npm run dev 56 | ``` 57 | 58 | - 打包 59 | 60 | ```bash 61 | npm run build 62 | ``` 63 | 64 | ## 如何贡献 65 | 66 | 非常欢迎你的加入![提一个 Issue](https://github.com/wuufeii/vue3-admin-vite/issues/new/choose) 或者提交一个 Pull Request。 67 | 68 | **Pull Request:** 69 | 70 | 1. Fork 代码! 71 | 2. 创建自己的分支: `git checkout -b feat/xxxx` 72 | 3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'` 73 | 4. 推送您的分支: `git push origin feat/xxxx` 74 | 5. 提交`pull request` 75 | 76 | ## Git 贡献提交规范 77 | 78 | - `feat` 增加新功能 79 | - `fix` 修复问题/BUG 80 | - `style` 代码风格相关无影响运行结果的 81 | - `perf` 优化/性能提升 82 | - `refactor` 重构 83 | - `revert` 撤销修改 84 | - `test` 测试相关 85 | - `docs` 文档/注释 86 | - `chore` 依赖更新/脚手架配置修改等 87 | - `workflow` 工作流改进 88 | - `ci` 持续集成 89 | - `types` 类型定义文件更改 90 | - `wip` 开发中 91 | 92 | ## 浏览器支持 93 | 94 | 本地开发推荐使用`Chrome 80+` 浏览器 95 | 96 | 支持现代浏览器, 不支持 IE 97 | 98 | | [ Edge](http://godban.github.io/browsers-support-badges/)
IE | [ Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | 99 | | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | 100 | | not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | 101 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 系统 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-vite-test", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "dependencies": { 9 | "element-plus": "^1.0.2-beta.70", 10 | "vue": "^3.0.4", 11 | "vue-router": "^4.0.0-0", 12 | "vuex": "^4.0.0-0" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^1.3.0", 16 | "@vitejs/plugin-vue-jsx": "^1.1.7", 17 | "@vue/compiler-sfc": "^3.1.5", 18 | "prettier": "^2.3.2", 19 | "sass": "1.26.2", 20 | "sass-loader": "8.0.2", 21 | "vite": "^2.4.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | // prettier前端代码格式化工具,需要配合插件 2 | // 使用命令格式化所有文件 npx prettier --write . 3 | module.exports = { 4 | printWidth: 100, 5 | tabWidth: 2, 6 | useTabs: false, 7 | semi: false, // 结尾不加分号 8 | singleQuote: true, // 使用单引号 9 | trailingComma: 'none', // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号) 10 | bracketSpacing: true, // true:{ foo: bar } false:{foo: bar} 11 | jsxBracketSameLine: false, // 在jsx中把'>' 是否单独放一行 12 | arrowParens: 'always', // 单个箭头函数参数是否加() 13 | htmlWhitespaceSensitivity: 'ignore', // 指定 HTML 文件的全局空白区域敏感度 14 | endOfLine: 'lf', // 结尾换行符 15 | vueIndentScriptAndStyle: false 16 | } 17 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuufeii/vue3-admin-vite/ee9435c925d85245489649b40d34f96f527fa878/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/css/common.scss: -------------------------------------------------------------------------------- 1 | /* 全局样式定义 */ 2 | :root { 3 | --border-light-color: #e4e7ed; 4 | --border-color: #d9d9d9; 5 | } 6 | .night-mode { 7 | --border-light-color: #404040; 8 | --border-color: #303030; 9 | } 10 | $borderLightColor: var(--border-light-color); 11 | $borderColor: var(--border-light-color); 12 | $_border_light: 1px solid $borderLightColor; 13 | $_border: 1px solid $borderColor; 14 | * { 15 | box-sizing: border-box; 16 | } 17 | html { 18 | font-size: 16px; 19 | } 20 | a { 21 | text-decoration: none; 22 | color: inherit; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | width: 100vw; 28 | height: 100vh; 29 | } 30 | 31 | .clearfix::after { 32 | display: table; 33 | content: ''; 34 | clear: both; 35 | } 36 | 37 | .mask-zIndex99 { 38 | position: fixed; 39 | top: 0; 40 | left: 0; 41 | width: 100%; 42 | height: 100%; 43 | z-index: 99; 44 | background-color: #00000033; 45 | } 46 | 47 | .flex-justify-around { 48 | display: flex; 49 | justify-content: space-around; 50 | } 51 | .flex-justify-between { 52 | display: flex; 53 | justify-content: space-between; 54 | } 55 | .flex-justify-center { 56 | display: flex; 57 | justify-content: center; 58 | } 59 | .border-left { 60 | border-left: $_border; 61 | } 62 | .border-right { 63 | border-right: $_border; 64 | } 65 | .border-bottom { 66 | border-bottom: $_border; 67 | } 68 | .border-bottom-light { 69 | border-bottom: $_border_light; 70 | } 71 | 72 | .hover-shadow:hover { 73 | box-shadow: 0 4px 12px 0 #0000001a; 74 | } 75 | 76 | ::-webkit-scrollbar { 77 | width: 7px; 78 | height: 8px; 79 | } 80 | ::-webkit-scrollbar-thumb { 81 | background-color: #9093994d; 82 | border-radius: 2px; 83 | box-shadow: inset 0 0 6px #0003; 84 | } 85 | ::-webkit-scrollbar-track { 86 | background-color: #0000000d; 87 | } 88 | -------------------------------------------------------------------------------- /src/assets/css/custom.scss: -------------------------------------------------------------------------------- 1 | @import './themes.scss'; 2 | :root { 3 | --el-color-primary: var(--systemThemeColor); 4 | } 5 | .el-button--default { 6 | --el-button-font-color: #ffffff; 7 | --el-button-background-color: var(--systemThemeColor); 8 | --el-button-border-color: var(--systemThemeColor); 9 | --el-button-hover-color: var(--systemThemeColor); 10 | --el-button-active-font-color: #e6e6e6; 11 | --el-button-active-background-color: var(--systemThemeColor); 12 | --el-button-active-border-color: var(--systemThemeColor); 13 | &:hover, 14 | &:focus { 15 | background-color: var(--systemThemeColor); 16 | color: #ffffff; 17 | border-color: var(--systemThemeColor); 18 | } 19 | } 20 | // ----------自定义switch---------- 21 | .custom-switch { 22 | --before-text: '\5f00'; 23 | --after-text: '\5173'; 24 | $beforeText: var(--before-text); 25 | $afterText: var(--after-text); 26 | .el-switch__core { 27 | position: relative; 28 | } 29 | &:not(.is-checked) .el-switch__core { 30 | &::after { 31 | content: $afterText; 32 | position: absolute; 33 | right: 5px; 34 | top: -2px; 35 | font-size: 12px; 36 | color: #fff; 37 | } 38 | } 39 | &.is-checked .el-switch__core { 40 | &::before { 41 | content: $beforeText; 42 | position: absolute; 43 | left: 5px; 44 | top: -1px; 45 | font-size: 12px; 46 | color: #fff; 47 | } 48 | } 49 | } 50 | // ----------END----------- 51 | 52 | // ----------自定义card---------- 53 | .el-card { 54 | --el-card-border-radius: 2px; 55 | --el-card-padding: 12px; 56 | .el-card__header { 57 | padding: 0 var(--el-card-padding); 58 | font-size: 0.875rem; 59 | min-height: 36px; 60 | display: flex; 61 | align-items: center; 62 | justify-content: space-between; 63 | } 64 | } 65 | // ----------END----------- 66 | .el-row.justify-between { 67 | margin-left: var(--gutter) !important; 68 | margin-right: var(--gutter) !important; 69 | } 70 | 71 | .night-mode { 72 | $itemColor: #151515; 73 | .el-drawer { 74 | --el-drawer-background-color: #151515; 75 | } 76 | .el-divider__text { 77 | background-color: var(--el-drawer-background-color); 78 | color: inherit; 79 | } 80 | .el-switch__label:not(.is-active) { 81 | color: $navbarColor; 82 | } 83 | .el-card { 84 | background-color: $itemColor; 85 | color: $navbarColor; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/assets/css/layout.scss: -------------------------------------------------------------------------------- 1 | @import './common.scss'; 2 | @import './themes.scss'; 3 | @import './mixin.scss'; 4 | #app { 5 | width: 100%; 6 | height: 100%; 7 | } 8 | .el-container { 9 | height: 100%; 10 | } 11 | .el-aside { 12 | height: inherit; 13 | transition: all 0.3s ease-in-out; 14 | } 15 | .el-logo { 16 | display: flex; 17 | align-items: center; 18 | height: $headerHeight; 19 | padding: 10px 4px 10px 16px; 20 | img { 21 | width: 32px; 22 | height: 32px; 23 | } 24 | .title { 25 | color: #ffffff; 26 | font-size: 16px; 27 | font-weight: 700; 28 | margin-left: 8px; 29 | @include ellipsis; 30 | &.is-hide { 31 | display: none; 32 | } 33 | } 34 | } 35 | .el-menu { 36 | height: inherit; 37 | background-color: $sidebarTheme; 38 | border-right: none; 39 | &.no-transition { 40 | transition: none; 41 | } 42 | .el-menu-item { 43 | color: $sidebarColor; 44 | padding-right: 0; 45 | @include ellipsis; 46 | &:hover { 47 | background-color: inherit; 48 | } 49 | &.is-active { 50 | color: #ffffff; 51 | background-color: $systemTheme; 52 | } 53 | } 54 | .el-submenu { 55 | .el-menu-item { 56 | min-width: 0; 57 | } 58 | &.is-active { 59 | & > .el-submenu__title { 60 | color: #ffffff; 61 | } 62 | } 63 | } 64 | .el-menu-item-group { 65 | background-color: $sidebarThemeGroup; 66 | } 67 | .el-submenu__title { 68 | color: $sidebarColor; 69 | &:hover { 70 | background-color: inherit; 71 | } 72 | & > span { 73 | display: inline-block; 74 | max-width: calc(100% - 45px); 75 | @include ellipsis; 76 | } 77 | } 78 | } 79 | .sidebar--white { 80 | $menu-color: $navbarColor; 81 | .el-menu-item { 82 | color: $menu-color; 83 | &:hover { 84 | color: $systemTheme; 85 | } 86 | &.is-active { 87 | background-color: $systemThemeActive; 88 | color: $systemTheme; 89 | } 90 | } 91 | &:not(.layout-type-3) { 92 | .el-menu:not(.el-menu--collapse) { 93 | .el-menu-item { 94 | &.is-active { 95 | border-right: 3px solid $systemTheme; 96 | } 97 | } 98 | } 99 | } 100 | .el-submenu__title { 101 | color: $menu-color; 102 | &:hover { 103 | color: $systemTheme; 104 | i { 105 | color: inherit; 106 | } 107 | } 108 | } 109 | .el-menu-item-group { 110 | background-color: #ffffff; 111 | } 112 | .el-submenu { 113 | &.is-active { 114 | & > .el-submenu__title { 115 | color: $systemTheme; 116 | > i { 117 | color: inherit; 118 | } 119 | } 120 | } 121 | } 122 | } 123 | .el-menu--popup { 124 | .el-menu-item, 125 | .el-submenu .el-submenu__title { 126 | height: 46px; 127 | line-height: 46px; 128 | } 129 | } 130 | 131 | .el-menu--collapse { 132 | .el-menu-item { 133 | &.is-active { 134 | background-color: inherit; 135 | box-shadow: 3px 0 0 $systemTheme inset; 136 | border-right: 0; 137 | color: $systemTheme; 138 | i { 139 | color: inherit !important; 140 | } 141 | } 142 | } 143 | .el-submenu { 144 | &.is-active { 145 | .el-submenu__title { 146 | color: $systemTheme; 147 | box-shadow: 3px 0 0 $systemTheme inset; 148 | i { 149 | color: inherit; 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | // ----------header---------- 157 | .el-header { 158 | height: $headerHeight; 159 | background-color: $navbarTheme; 160 | color: #ffffff; 161 | padding: 0; 162 | .header-content { 163 | display: flex; 164 | width: 100%; 165 | height: 100%; 166 | align-items: center; 167 | } 168 | .header { 169 | width: 100%; 170 | height: 100%; 171 | display: flex; 172 | justify-content: space-between; 173 | align-items: center; 174 | &.has-logo { 175 | width: calc(100% - #{$logoWidth}); 176 | } 177 | } 178 | .header-left { 179 | height: 100%; 180 | display: flex; 181 | align-items: center; 182 | > svg { 183 | width: 32px; 184 | } 185 | } 186 | .header-right { 187 | display: flex; 188 | align-items: center; 189 | height: 100%; 190 | > svg { 191 | width: 32px; 192 | } 193 | } 194 | .navbar-icon { 195 | width: 1em; 196 | height: 1em; 197 | cursor: pointer; 198 | } 199 | .el-breadcrumb { 200 | margin-left: 10px; 201 | color: #ffffff; 202 | span a, 203 | span { 204 | color: inherit !important; 205 | } 206 | &:not(.is-show) { 207 | display: none; 208 | } 209 | } 210 | } 211 | .navbar--white { 212 | .el-header { 213 | color: $navbarColor; 214 | } 215 | .el-breadcrumb { 216 | color: $navbarColor; 217 | } 218 | .header-user { 219 | color: $navbarColor; 220 | } 221 | } 222 | 223 | // ----------main---------- 224 | .el-main { 225 | background-color: $contentBgColor; 226 | padding: $paddingWidth; 227 | } 228 | 229 | .sidebar--white { 230 | &.layout-type-1 { 231 | .el-logo { 232 | border-bottom: $border; 233 | span { 234 | color: $systemTheme; 235 | } 236 | } 237 | } 238 | .el-aside > .el-menu { 239 | border-right: $border; 240 | } 241 | } 242 | .navbar--white { 243 | .header { 244 | border-bottom: $border; 245 | } 246 | } 247 | 248 | .layout-type-2 { 249 | .el-logo { 250 | width: $logoWidth; 251 | } 252 | &.navbar--white { 253 | .el-logo { 254 | border-bottom: $border; 255 | .title { 256 | color: $systemTheme; 257 | } 258 | } 259 | } 260 | .el-logo { 261 | .title { 262 | display: inline-block; 263 | } 264 | } 265 | } 266 | 267 | .layout-type-3 { 268 | $_sidebarColor: #ffffffa6; 269 | .el-logo { 270 | width: $logoWidth; 271 | .title { 272 | color: #ffffff; 273 | &.is-hide { 274 | display: inline-block; 275 | } 276 | } 277 | } 278 | &.navbar--white { 279 | .el-logo { 280 | border-bottom: $border; 281 | .title { 282 | color: $systemTheme; 283 | } 284 | } 285 | } 286 | .navbar-icon._fold { 287 | display: none; 288 | } 289 | .el-breadcrumb { 290 | display: none; 291 | } 292 | &:not(.navbar--white) .el-menu { 293 | .el-menu-item { 294 | background-color: inherit; 295 | color: $_sidebarColor; 296 | i { 297 | color: $_sidebarColor; 298 | } 299 | &:hover:not(.is-disabled):focus, 300 | &:hover:not(.is-disabled):hover { 301 | color: #ffffff; 302 | } 303 | } 304 | .el-submenu__title { 305 | background-color: inherit; 306 | color: $_sidebarColor; 307 | i { 308 | color: $_sidebarColor; 309 | } 310 | &:hover { 311 | color: #ffffff; 312 | } 313 | } 314 | } 315 | .el-menu--horizontal { 316 | background-color: transparent; 317 | border-bottom: none; 318 | & > .el-menu { 319 | background-color: $navbarColor; 320 | .el-submenu { 321 | background-color: inherit; 322 | &.is-active { 323 | .el-submenu__title { 324 | color: $systemTheme !important; 325 | } 326 | } 327 | } 328 | } 329 | & > .el-menu-item, 330 | & > .el-submenu .el-submenu__title { 331 | height: $headerHeight; 332 | line-height: $headerHeight; 333 | color: $sidebarColor; 334 | &:hover { 335 | color: #ffffff; 336 | i { 337 | color: #ffffff; 338 | } 339 | } 340 | } 341 | & > .el-menu-item { 342 | border-bottom: none; 343 | &:hover { 344 | color: #ffffff; 345 | background-color: $sidebarThemeActive; 346 | i { 347 | color: #ffffff; 348 | } 349 | } 350 | &.is-active { 351 | background-color: $sidebarThemeActive; 352 | color: #ffffff; 353 | i { 354 | color: #ffffff; 355 | } 356 | } 357 | } 358 | & > .el-submenu { 359 | &.is-active { 360 | .el-submenu__title { 361 | color: #ffffff; 362 | background-color: $sidebarThemeActive; 363 | border-bottom: none; 364 | } 365 | } 366 | .el-submenu__title { 367 | &:hover { 368 | background-color: $sidebarThemeActive; 369 | } 370 | } 371 | } 372 | .el-menu { 373 | .el-menu-item.is-active, 374 | .el-submenu.is-active > .el-submenu__title { 375 | color: #ffffff; 376 | } 377 | .el-menu-item.is-active { 378 | background-color: $systemTheme; 379 | } 380 | } 381 | } 382 | &.navbar--white { 383 | .el-menu--horizontal { 384 | .el-menu-item { 385 | &:hover { 386 | color: $systemTheme; 387 | background-color: #ffffff; 388 | i { 389 | color: inherit; 390 | } 391 | } 392 | &.is-active { 393 | color: $systemTheme; 394 | border-bottom: 2px solid $systemTheme; 395 | background-color: #ffffff; 396 | i { 397 | color: inherit; 398 | } 399 | } 400 | } 401 | & > .el-menu-item { 402 | color: $navbarColor; 403 | } 404 | & > .el-submenu { 405 | &.is-active { 406 | .el-submenu__title { 407 | color: $systemTheme; 408 | background-color: #ffffff; 409 | border-bottom: 2px solid $systemTheme; 410 | i { 411 | color: inherit; 412 | } 413 | } 414 | } 415 | .el-submenu__title { 416 | color: $navbarColor; 417 | &:hover { 418 | color: $systemTheme; 419 | background-color: #ffffff; 420 | i { 421 | color: inherit; 422 | } 423 | } 424 | } 425 | } 426 | .el-menu { 427 | background-color: #ffffff; 428 | .el-menu-item { 429 | &.is-active { 430 | background-color: $systemThemeActive; 431 | border-bottom: none; 432 | } 433 | } 434 | .el-submenu { 435 | &.is-active { 436 | .el-submenu__title { 437 | color: $systemTheme; 438 | i { 439 | color: inherit; 440 | } 441 | } 442 | } 443 | } 444 | & > .el-submenu { 445 | .el-submenu__title { 446 | &:hover { 447 | color: $systemTheme; 448 | i { 449 | color: inherit; 450 | } 451 | } 452 | } 453 | } 454 | } 455 | } 456 | } 457 | } 458 | 459 | .layout-type-4 { 460 | .el-logo { 461 | padding-left: 23px; 462 | .title { 463 | display: none; 464 | } 465 | } 466 | ._fold { 467 | display: none; 468 | } 469 | .el-menu--collapse { 470 | width: 100%; 471 | .el-menu-item { 472 | &.is-active { 473 | span { 474 | color: $systemTheme; 475 | } 476 | } 477 | } 478 | & > .el-menu-item { 479 | height: 70px; 480 | line-height: 70px; 481 | } 482 | & > .el-submenu { 483 | &.is-active { 484 | & > .el-submenu__title { 485 | span { 486 | color: $systemTheme; 487 | } 488 | } 489 | } 490 | & > .el-submenu__title { 491 | height: 70px; 492 | line-height: 70px; 493 | } 494 | } 495 | 496 | & > .el-menu-item > div, 497 | .el-submenu__title { 498 | display: flex !important; 499 | flex-direction: column; 500 | justify-content: center; 501 | align-items: center; 502 | padding: 0 !important; 503 | i { 504 | color: $sidebarColor; 505 | } 506 | > span { 507 | color: $sidebarColor; 508 | width: 100%; 509 | height: 30px; 510 | font-size: 12px; 511 | visibility: visible; 512 | display: flex; 513 | justify-content: center; 514 | align-items: center; 515 | } 516 | } 517 | } 518 | &.sidebar--white { 519 | .el-menu--collapse { 520 | & > .el-menu-item:not(.is-active) > div, 521 | .el-submenu:not(.is-active) .el-submenu__title { 522 | i { 523 | color: $navbarColor; 524 | } 525 | > span { 526 | color: $navbarColor; 527 | } 528 | } 529 | } 530 | } 531 | } 532 | 533 | // ----------tabs---------- 534 | .tabs-content { 535 | $_tabsHeight: 30px; 536 | $_svgWidth: 32px; 537 | display: flex; 538 | align-items: center; 539 | height: $_tabsHeight; 540 | &:not(.is-show) { 541 | display: none; 542 | } 543 | .tabs-svg { 544 | width: $_svgWidth; 545 | height: calc(#{$_tabsHeight} - 4px); 546 | padding: 6px 0; 547 | color: rgba(0, 0, 0, 0.45); 548 | cursor: pointer; 549 | &:hover { 550 | color: #000; 551 | } 552 | } 553 | .tabs-scroll { 554 | width: calc(100% - #{$_svgWidth}); 555 | height: 100%; 556 | top: 2px; 557 | position: relative; 558 | overflow: hidden; 559 | .el-tabs__nav-wrap.is-scrollable { 560 | padding: 0 $_svgWidth + 1px; 561 | } 562 | .el-tabs__header { 563 | margin: 0; 564 | border-bottom: 0; 565 | } 566 | .el-tabs__content { 567 | display: none; 568 | } 569 | .el-tabs__nav { 570 | border: none; 571 | } 572 | .el-tabs__nav-next, 573 | .el-tabs__nav-prev { 574 | line-height: $_tabsHeight - 4px; 575 | font-size: 14px; 576 | width: $_svgWidth; 577 | text-align: center; 578 | &:not(.is-disabled):hover { 579 | color: #000; 580 | } 581 | &.is-disabled { 582 | opacity: 0.4; 583 | cursor: not-allowed; 584 | } 585 | } 586 | .el-tabs__item { 587 | height: $_tabsHeight - 4px; 588 | line-height: $_tabsHeight - 5px; 589 | font-size: 12px; 590 | margin: 0 2px; 591 | border: 1px solid #d9d9d9; 592 | border-radius: 2px; 593 | padding: 0 10px !important; 594 | &:first-child { 595 | border-left: 1px solid #d9d9d9; 596 | } 597 | &:last-child { 598 | border-right: 1px solid #d9d9d9; 599 | } 600 | &.is-active { 601 | border-color: $systemTheme; 602 | background-color: $systemTheme; 603 | color: #ffffff; 604 | } 605 | } 606 | } 607 | } 608 | .tabs-menu { 609 | padding: 6px 0; 610 | .el-dropdown-menu__item { 611 | line-height: 30px; 612 | padding: 0 15px; 613 | display: flex; 614 | align-items: center; 615 | &:not(.is-disabled):hover { 616 | background-color: #f2f2f2; 617 | } 618 | } 619 | .el-divider { 620 | margin: 6px 0; 621 | } 622 | i { 623 | font-size: 18px; 624 | &.left { 625 | transform: rotate(90deg); 626 | } 627 | &.right { 628 | transform: rotate(-90deg); 629 | } 630 | } 631 | } 632 | .night-mode { 633 | .header { 634 | border-bottom: 1px solid $borderColor; 635 | } 636 | .tabs-content { 637 | background-color: $navbarTheme; 638 | .el-tabs__item { 639 | color: $navbarColor; 640 | border-color: $borderColor; 641 | } 642 | } 643 | } 644 | 645 | // ----------drawer---------- 646 | .el-drawer { 647 | max-width: 400px; 648 | min-width: 300px; 649 | } 650 | .el-drawer__header { 651 | margin-bottom: 0; 652 | padding: $paddingWidth !important; 653 | border-bottom: $border; 654 | & > span { 655 | font-size: 16px; 656 | font-weight: 500; 657 | } 658 | .el-drawer__close-btn > i { 659 | font-size: 20px; 660 | } 661 | } 662 | .el-drawer__body { 663 | padding: $paddingWidth; 664 | } 665 | 666 | // ----------sidebar menu background color fix---------- 667 | .el-aside>.el-menu { 668 | overflow: auto; 669 | } -------------------------------------------------------------------------------- /src/assets/css/mixin.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 溢出省略号 3 | * @param {Number} 行数 4 | */ 5 | @mixin ellipsis($rowCount: 1) { 6 | @if $rowCount <=1 { 7 | overflow: hidden; 8 | text-overflow: ellipsis; 9 | white-space: nowrap; 10 | } @else { 11 | min-width: 0; 12 | overflow: hidden; 13 | text-overflow: ellipsis; 14 | display: -webkit-box; 15 | -webkit-line-clamp: $rowCount; 16 | -webkit-box-orient: vertical; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/assets/css/themes.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --systemThemeColor: #0960bd; 3 | --navbarThemeColor: #ffffff; 4 | --navbarColor: rgba(0, 0, 0, 0.85); // 当navbar背景为白色时 5 | --sidebarThemeColor: #001529; 6 | --sidebarThemeColorActive: #0960bd; 7 | --systemThemeColorActive: #93ffff; 8 | --contentBgColor: #f0f2f5; 9 | --border-color: #f0f0f0; 10 | 11 | --el-border-color-base: #dcdfe6; 12 | } 13 | 14 | .night-mode { 15 | --navbarThemeColor: #1f1f1f; 16 | --navbarColor: #ffffff; 17 | --sidebarThemeColor: #1f1f1f; 18 | --contentBgColor: #000000; 19 | --border-color: #303030; 20 | --el-border-color-base: #303030; 21 | 22 | --el-border-color-light: #303030; 23 | } 24 | 25 | $systemTheme: var(--systemThemeColor); 26 | $navbarTheme: var(--navbarThemeColor); 27 | $sidebarTheme: var(--sidebarThemeColor); 28 | $sidebarThemeActive: var(--sidebarThemeColorActive); 29 | $systemThemeActive: var(--systemThemeColorActive); 30 | $sidebarThemeGroup: #444b51; 31 | $sidebarThemeBg: #001529; 32 | $sidebarColor: #ffffffb3; 33 | $navbarColor: var(--navbarColor); 34 | 35 | $contentBgColor: var(--contentBgColor); 36 | 37 | $headerHeight: 48px; 38 | $logoWidth: 200px; 39 | 40 | // 边距 41 | $marginWidth: 1rem; 42 | $paddingWidth: 1rem; 43 | @media screen and(max-width: 1000px) { 44 | $paddingWidth: 10px; 45 | } 46 | 47 | // 边框 48 | $borderColor: var(--border-color); 49 | $border: 1px solid $borderColor; 50 | -------------------------------------------------------------------------------- /src/assets/img/user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuufeii/vue3-admin-vite/ee9435c925d85245489649b40d34f96f527fa878/src/assets/img/user.jpg -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuufeii/vue3-admin-vite/ee9435c925d85245489649b40d34f96f527fa878/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/svg/full-screen-max.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/full-screen-min.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/language.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/SvgIcon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 29 | 30 | 38 | -------------------------------------------------------------------------------- /src/components/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/components/layout/components/Logo.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 25 | -------------------------------------------------------------------------------- /src/components/layout/components/layout.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './navbar/index.vue' 2 | export { default as Sidebar } from './sidebar/index.vue' 3 | export { default as Tabs } from './tabs/index.vue' 4 | -------------------------------------------------------------------------------- /src/components/layout/components/navbar/FullScreen.vue: -------------------------------------------------------------------------------- 1 | 12 | ./test 13 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/layout/components/navbar/UserInfo.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | 21 | 39 | -------------------------------------------------------------------------------- /src/components/layout/components/navbar/fullScreen.js: -------------------------------------------------------------------------------- 1 | export const changeFullScreen = (data) => { 2 | let element = document.documentElement 3 | if (data.fullscreen) { 4 | if (document.exitFullscreen) { 5 | document.exitFullscreen() 6 | } else if (document.webkitCancelFullScreen) { 7 | document.webkitCancelFullScreen() 8 | } else if (document.mozCancelFullScreen) { 9 | document.mozCancelFullScreen() 10 | } else if (document.msExitFullscreen) { 11 | document.msExitFullscreen() 12 | } 13 | } else { 14 | if (element.requestFullscreen) { 15 | element.requestFullscreen() 16 | } else if (element.webkitRequestFullScreen) { 17 | element.webkitRequestFullScreen() 18 | } else if (element.mozRequestFullScreen) { 19 | element.mozRequestFullScreen() 20 | } else if (element.msRequestFullscreen) { 21 | element.msRequestFullscreen() 22 | } 23 | } 24 | } 25 | 26 | export const listenerEvent = (fn, data) => { 27 | document.onkeydown = (event) => { 28 | if (event.keyCode == 122) { 29 | event.preventDefault() 30 | changeFullScreen(data) 31 | } 32 | } 33 | document.addEventListener('fullscreenchange', fn) 34 | document.addEventListener('mozfullscreenchange', fn) 35 | document.addEventListener('webkitfullscreenchange', fn) 36 | document.addEventListener('msfullscreenchange', fn) 37 | } 38 | -------------------------------------------------------------------------------- /src/components/layout/components/navbar/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/components/layout/components/setting/index.js: -------------------------------------------------------------------------------- 1 | import { setThemes, getThemes } from 'utils/storage' 2 | // 定义变量 3 | export const _data = { 4 | drawer: false, 5 | nightMode: false, 6 | navbarType: '左侧菜单模式', 7 | navbarList: ['左侧菜单模式', '顶部菜单混合模式', '顶部菜单模式', '左侧菜单混合模式'], 8 | systemThemeColor: '#0960BD', 9 | systemThemeList: [ 10 | '#0960BD', 11 | '#0084F4', 12 | '#009688', 13 | '#536DF3', 14 | '#FF5C93', 15 | '#EE4F12', 16 | '#0096C7', 17 | '#9C27B0', 18 | '#FF9800' 19 | ], 20 | navbarThemeColor: '#FFFFFF', 21 | navbarThemeList: [ 22 | '#FFFFFF', 23 | '#151515', 24 | '#009688', 25 | '#5172DC', 26 | '#409EFF', 27 | '#E74C3C', 28 | '#24292E', 29 | '#394664', 30 | '#001529', 31 | '#383F45' 32 | ], 33 | sidebarThemeColor: '#001529', 34 | sidebarThemeList: [ 35 | '#001529', 36 | '#212121', 37 | '#273352', 38 | '#FFFFFF', 39 | '#191B24', 40 | '#191A23', 41 | '#304156', 42 | '#28333E', 43 | '#344058', 44 | '#383F45' 45 | ], 46 | showBreadcrumb: true, 47 | showTabs: true 48 | } 49 | 50 | // 主题切换 51 | export const _changeSetting = (params) => { 52 | let { type, value, store, data } = params 53 | data[type] = value 54 | settingThemes({ type, value }) 55 | if (type === 'navbarType') { 56 | data.drawer = false 57 | store.commit('getNavbarType', value) 58 | } 59 | } 60 | 61 | // 设置主题并暂存缓存 62 | const settingThemes = (params) => { 63 | const { type, value } = params 64 | let themes = getThemes() 65 | themes[type] = value 66 | setThemes(themes) 67 | _getThemes() 68 | } 69 | 70 | // 读取主题 71 | export const _getThemes = (params) => { 72 | let themes = getThemes() 73 | if (params) { 74 | let { data } = params 75 | Object.keys(data).forEach((key) => { 76 | data[key] = themes[key] || data[key] 77 | }) 78 | } 79 | let attribute = '' 80 | if (themes.systemThemeColor) { 81 | attribute += `--systemThemeColor: ${themes.systemThemeColor};` 82 | let result = lighten(themes.systemThemeColor, 58) 83 | attribute += `--systemThemeColorActive: ${result};` 84 | } 85 | if (themes.navbarThemeColor) { 86 | attribute += `--navbarThemeColor: ${themes.navbarThemeColor};` 87 | let result = lighten(themes.navbarThemeColor, 6) 88 | attribute += `--sidebarThemeColorActive: ${result};` 89 | } 90 | if (themes.sidebarThemeColor) { 91 | attribute += `--sidebarThemeColor: ${themes.sidebarThemeColor};` 92 | toggleClass({ 93 | flag: themes.sidebarThemeColor === '#FFFFFF', 94 | cls: 'sidebar--white' 95 | }) 96 | } 97 | toggleClass({ 98 | flag: themes.nightMode, 99 | cls: 'night-mode' 100 | }) 101 | toggleClass({ 102 | flag: themes.showBreadcrumb, 103 | dom: '.el-breadcrumb', 104 | cls: 'is-show' 105 | }) 106 | toggleClass({ 107 | flag: themes.showTabs, 108 | dom: '.tabs-content', 109 | cls: 'is-show' 110 | }) 111 | toggleClass({ 112 | flag: themes?.navbarThemeColor ? themes.navbarThemeColor === '#FFFFFF' : true, 113 | cls: 'navbar--white' 114 | }) 115 | _data.navbarList.forEach((item, index) => { 116 | toggleClass({ 117 | flag: item === _data.navbarType, 118 | cls: `layout-type-${index + 1}` 119 | }) 120 | }) 121 | 122 | document.querySelector(':root').setAttribute('style', attribute) 123 | } 124 | 125 | // 添加移除class 126 | const toggleClass = (params) => { 127 | let { flag, cls, dom } = params 128 | dom = dom || 'body' 129 | let classList = document.querySelector(dom)?.classList 130 | flag ? classList?.add(cls) : classList?.remove(cls) 131 | } 132 | 133 | // 颜色计算方法 134 | const lighten = (color, amount) => { 135 | color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color 136 | amount = Math.trunc((255 * amount) / 100) 137 | return `#${addLight(color.substring(0, 2), amount)}${addLight( 138 | color.substring(2, 4), 139 | amount 140 | )}${addLight(color.substring(4, 6), amount)}` 141 | } 142 | 143 | // 颜色计算方法 144 | const addLight = (color, amount) => { 145 | const cc = parseInt(color, 16) + amount 146 | const c = cc > 255 ? 255 : cc 147 | return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}` 148 | } 149 | -------------------------------------------------------------------------------- /src/components/layout/components/setting/index.vue: -------------------------------------------------------------------------------- 1 | 95 | 96 | 120 | 121 | 274 | -------------------------------------------------------------------------------- /src/components/layout/components/sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/components/layout/components/sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /src/components/layout/components/tabs/index.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /src/components/layout/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import ElementPlus from 'element-plus' 6 | import 'element-plus/lib/theme-chalk/index.css' 7 | import 'assets/css/layout.scss' 8 | import 'assets/css/custom.scss' 9 | import SvgIcon from 'components/SvgIcon.vue' 10 | 11 | let app = createApp(App) 12 | app.use(store) 13 | app.use(router) 14 | app.use(ElementPlus) 15 | app.component('SvgIcon', SvgIcon) 16 | app.mount('#app') 17 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import Layout from 'components/layout/index.vue' 3 | 4 | const routes = [ 5 | { 6 | path: '/', 7 | component: Layout, 8 | children: [ 9 | { 10 | path: 'home', 11 | name: 'Home', 12 | component: () => import('@/views/Home.vue') 13 | } 14 | ] 15 | } 16 | ] 17 | 18 | const router = createRouter({ 19 | history: createWebHistory(), 20 | routes 21 | }) 22 | export default router 23 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | import { getThemes } from 'utils/storage' 3 | 4 | const navbarType = getThemes()?.navbarType ?? '左侧菜单模式' 5 | export default createStore({ 6 | state: { 7 | navbarType: navbarType, 8 | isCollapse: false, 9 | activeMenu: '' 10 | }, 11 | mutations: { 12 | // 获取导航栏类型 13 | getNavbarType(state, data) { 14 | state.navbarType = data 15 | }, 16 | 17 | // 获取菜单折叠 18 | getCollapse(state, data) { 19 | state.isCollapse = data 20 | }, 21 | 22 | // 获取当前选中菜单 23 | getActiveMenu(state, data) { 24 | state.activeMenu = data 25 | } 26 | }, 27 | actions: {}, 28 | modules: {} 29 | }) 30 | -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | // 设置导航 2 | export const setTabs = (data, value) => { 3 | if (value) { 4 | data.forEach((item) => (item.active = item.id === value)) 5 | } 6 | sessionStorage.setItem('TABS_LIST', JSON.stringify(data)) 7 | } 8 | 9 | // 获取导航 10 | export const getTabs = () => { 11 | let result = sessionStorage.getItem('TABS_LIST') 12 | result = result ? JSON.parse(result) : [] 13 | return result 14 | } 15 | 16 | // 设置主题 17 | export const setThemes = (data) => { 18 | localStorage.setItem('THEMES', JSON.stringify(data)) 19 | } 20 | 21 | // 获取主题 22 | export const getThemes = () => { 23 | let result = localStorage.getItem('THEMES') 24 | result = result 25 | ? JSON.parse(result) 26 | : { 27 | showBreadcrumb: true, 28 | showTabs: true 29 | } 30 | return result 31 | } 32 | 33 | // 设置面包屑 34 | export const setBreadcrumb = (data) => { 35 | data = data || [] 36 | data = data.reverse() 37 | sessionStorage.setItem('BREADCRUMB', JSON.stringify(data)) 38 | } 39 | 40 | // 获取面包屑 41 | export const getBreadcrumb = () => { 42 | let result = sessionStorage.getItem('BREADCRUMB') 43 | result = result ? JSON.parse(result) : [] 44 | return result 45 | } 46 | -------------------------------------------------------------------------------- /src/utils/svg-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @From https://github.com/JetBrains/svg-sprite-loader/issues/434 3 | * @Better https://github.com/anncwb/vite-plugin-svg-icons 4 | */ 5 | import { readFileSync, readdirSync } from 'fs' 6 | 7 | let idPrefix = '' 8 | 9 | const hasViewBox = /(viewBox="[^>+].*?")/g 10 | 11 | const clearHeightWidth = /(width|height)="([^>+].*?)"/g 12 | 13 | const svgTitle = /+].*?)>/ 14 | 15 | const clearReturn = /(\r)|(\n)/g 16 | 17 | // dir需以/结尾 18 | function findSvgFile(dir) { 19 | const svgRes = [] 20 | // 读取目标目录 21 | const dirents = readdirSync(dir, { withFileTypes: true }) 22 | for (const dirent of dirents) { 23 | if (dirent.isDirectory()) { 24 | // 是目录则递归遍历目录 25 | svgRes.push(...findSvgFile(dir + dirent.name + '/')) 26 | } else { 27 | if (!dirent.name.endsWith('.svg')) continue 28 | const svg = readFileSync(dir + dirent.name) 29 | .toString() 30 | .replace('', '') 31 | .replace(clearReturn, '') 32 | //提取出svg标签,并根据id规则生成 33 | .replace(svgTitle, ($1, $2) => { 34 | let width = 0 35 | let height = 0 36 | // 获取svg标签里的属性 如 xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" 37 | let content = $2.replace(clearHeightWidth, (s1, s2, s3) => { 38 | if (s2 === 'width') { 39 | width = s3 40 | } else if (s2 === 'height') { 41 | height = s3 42 | } 43 | return '' 44 | }) 45 | // 用 width/height属性来替换viewBox的值 46 | if (!hasViewBox.test($2)) { 47 | content += `viewBox="0 0 ${width} ${height}"` 48 | } 49 | // 将svg的文件名和id-prefix拼接成标签的id 50 | return `` 51 | }) 52 | // 将闭合标签/svg也换成symbol 53 | .replace('', '') 54 | svgRes.push(svg) 55 | } 56 | } 57 | return svgRes 58 | } 59 | 60 | export const svgLoader = (path, prefix = 'icon') => { 61 | if (path === '') return 62 | idPrefix = prefix 63 | const res = findSvgFile(process.cwd() + path) 64 | return { 65 | name: 'svg-transform', 66 | transformIndexHtml(html) { 67 | return html.replace( 68 | '', 69 | ` ` 72 | ) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 93 | 94 | 106 | 107 | 156 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import pkg from './package.json' 3 | import vue from '@vitejs/plugin-vue' 4 | // vue-jsx 插件说明 https://github.com/vuejs/jsx-next/blob/dev/packages/babel-plugin-jsx/README-zh_CN.md 5 | import vueJsx from '@vitejs/plugin-vue-jsx' 6 | import { svgLoader } from './src/utils/svg-loader.js' 7 | 8 | const { dependencies, devDependencies, name, version } = pkg 9 | 10 | const __APP_INFO__ = { 11 | pkg: { dependencies, devDependencies, name, version }, 12 | lastBuildTime: new Date().toLocaleString() 13 | } 14 | 15 | // 官方文档 https://cn.vitejs.dev/config/ 16 | export default ({ command }) => { 17 | return { 18 | base: './', 19 | 20 | server: { 21 | port: 3000, 22 | open: true 23 | }, 24 | 25 | build: { 26 | brotliSize: false // 禁用 brotli 压缩大小报告,以提高大型项目的构建性能。 27 | }, 28 | 29 | plugins: [vue(), vueJsx({}), svgLoader('/src/assets/svg/')], 30 | 31 | resolve: { 32 | alias: { 33 | '@': resolve(__dirname, 'src'), // 别名 `@` 指向 `src` 目录 PS:IDEA编辑器还是不能识别 34 | assets: '/src/assets', 35 | components: '/src/components', 36 | views: '/src/views', 37 | utils: '/src/utils' 38 | } 39 | }, 40 | 41 | // 定义全局常量替换方式,其中每项在开发环境下会被定义在全局,而在构建时被静态替换 42 | define: { 43 | __APP_INFO__: JSON.stringify(__APP_INFO__) 44 | }, 45 | 46 | css: { 47 | preprocessorOptions: { 48 | scss: { 49 | // additionalData: '@import "@/styles/_variables";', 50 | javascriptEnabled: true 51 | } 52 | } 53 | } 54 | } 55 | } 56 | --------------------------------------------------------------------------------